fix indentation (tabs to 4 spaces) (oops)
This commit is contained in:
parent
fe4a788898
commit
23a02617f9
38 changed files with 447 additions and 447 deletions
|
@ -1,17 +1,17 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func accountHandler(app *model.AppState) http.Handler {
|
func accountHandler(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
|
|
108
admin/http.go
108
admin/http.go
|
@ -1,20 +1,20 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Handler(app *model.AppState) http.Handler {
|
func Handler(app *model.AppState) http.Handler {
|
||||||
|
@ -274,20 +274,20 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if account.Locked {
|
if account.Locked {
|
||||||
controller.SetSessionError(app.DB, session, "This account is locked.")
|
controller.SetSessionError(app.DB, session, "This account is locked.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password))
|
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" attempted login with incorrect password. (%s)", account.Username, controller.ResolveIP(app, r))
|
app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" attempted login with incorrect password. (%s)", account.Username, controller.ResolveIP(app, r))
|
||||||
if locked := handleFailedLogin(app, account); locked {
|
if locked := handleFailedLogin(app, account); locked {
|
||||||
controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
|
controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
|
||||||
} else {
|
} else {
|
||||||
controller.SetSessionError(app.DB, session, "Invalid username or password.")
|
controller.SetSessionError(app.DB, session, "Invalid username or password.")
|
||||||
}
|
}
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -308,8 +308,8 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.SetSessionMessage(app.DB, session, "")
|
controller.SetSessionMessage(app.DB, session, "")
|
||||||
controller.SetSessionError(app.DB, session, "")
|
controller.SetSessionError(app.DB, session, "")
|
||||||
http.Redirect(w, r, "/admin/totp", http.StatusFound)
|
http.Redirect(w, r, "/admin/totp", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -389,13 +389,13 @@ func loginTOTPHandler(app *model.AppState) http.Handler {
|
||||||
}
|
}
|
||||||
if totpMethod == nil {
|
if totpMethod == nil {
|
||||||
app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" failed login (Incorrect TOTP). (%s)", session.AttemptAccount.Username, controller.ResolveIP(app, r))
|
app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" failed login (Incorrect TOTP). (%s)", session.AttemptAccount.Username, controller.ResolveIP(app, r))
|
||||||
if locked := handleFailedLogin(app, session.AttemptAccount); locked {
|
if locked := handleFailedLogin(app, session.AttemptAccount); locked {
|
||||||
controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
|
controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
|
||||||
controller.SetSessionAttemptAccount(app.DB, session, nil)
|
controller.SetSessionAttemptAccount(app.DB, session, nil)
|
||||||
http.Redirect(w, r, "/admin", http.StatusFound)
|
http.Redirect(w, r, "/admin", http.StatusFound)
|
||||||
} else {
|
} else {
|
||||||
controller.SetSessionError(app.DB, session, "Incorrect TOTP.")
|
controller.SetSessionError(app.DB, session, "Incorrect TOTP.")
|
||||||
}
|
}
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -515,27 +515,27 @@ func enforceSession(app *model.AppState, next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFailedLogin(app *model.AppState, account *model.Account) bool {
|
func handleFailedLogin(app *model.AppState, account *model.Account) bool {
|
||||||
locked, err := controller.IncrementAccountFails(app.DB, account.ID)
|
locked, err := controller.IncrementAccountFails(app.DB, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
os.Stderr,
|
os.Stderr,
|
||||||
"WARN: Failed to increment login failures for \"%s\": %v\n",
|
"WARN: Failed to increment login failures for \"%s\": %v\n",
|
||||||
account.Username,
|
account.Username,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
app.Log.Warn(
|
app.Log.Warn(
|
||||||
log.TYPE_ACCOUNT,
|
log.TYPE_ACCOUNT,
|
||||||
"Failed to increment login failures for \"%s\"",
|
"Failed to increment login failures for \"%s\"",
|
||||||
account.Username,
|
account.Username,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if locked {
|
if locked {
|
||||||
app.Log.Warn(
|
app.Log.Warn(
|
||||||
log.TYPE_ACCOUNT,
|
log.TYPE_ACCOUNT,
|
||||||
"Account \"%s\" was locked: %d failed login attempts",
|
"Account \"%s\" was locked: %d failed login attempts",
|
||||||
account.Username,
|
account.Username,
|
||||||
model.MAX_LOGIN_FAIL_ATTEMPTS,
|
model.MAX_LOGIN_FAIL_ATTEMPTS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return locked
|
return locked
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func logsHandler(app *model.AppState) http.Handler {
|
func logsHandler(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveRelease(app *model.AppState) http.Handler {
|
func serveRelease(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var indexTemplate = template.Must(template.ParseFiles(
|
var indexTemplate = template.Must(template.ParseFiles(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
|
|
16
api/api.go
16
api/api.go
|
@ -1,15 +1,15 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Handler(app *model.AppState) http.Handler {
|
func Handler(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,66 +1,66 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeAllArtists(app *model.AppState) 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(app.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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err = encoder.Encode(artists)
|
err = encoder.Encode(artists)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeArtist(app *model.AppState, 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 {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
|
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
|
||||||
Artwork string `json:"artwork"`
|
Artwork string `json:"artwork"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
artistJSON struct {
|
artistJSON struct {
|
||||||
*model.Artist
|
*model.Artist
|
||||||
Credits map[string]creditJSON `json:"credits"`
|
Credits map[string]creditJSON `json:"credits"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
show_hidden_releases := session != nil && session.Account != nil
|
show_hidden_releases := session != nil && session.Account != nil
|
||||||
|
|
||||||
dbCredits, err := controller.GetArtistCredits(app.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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var credits = map[string]creditJSON{}
|
var credits = map[string]creditJSON{}
|
||||||
for _, credit := range dbCredits {
|
for _, credit := range dbCredits {
|
||||||
|
@ -74,17 +74,17 @@ func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err = encoder.Encode(artistJSON{
|
err = encoder.Encode(artistJSON{
|
||||||
Artist: artist,
|
Artist: artist,
|
||||||
Credits: credits,
|
Credits: credits,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateArtist(app *model.AppState) http.Handler {
|
func CreateArtist(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeRelease(app *model.AppState, 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 {
|
||||||
|
@ -116,15 +116,15 @@ func ServeRelease(app *model.AppState, release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err := encoder.Encode(response)
|
err := encoder.Encode(response)
|
||||||
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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeCatalog(app *model.AppState) http.Handler {
|
func ServeCatalog(app *model.AppState) http.Handler {
|
||||||
|
|
36
api/track.go
36
api/track.go
|
@ -1,13 +1,13 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -29,7 +29,7 @@ func ServeAllTracks(app *model.AppState) http.Handler {
|
||||||
dbTracks, err := controller.GetAllTracks(app.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range dbTracks {
|
for _, track := range dbTracks {
|
||||||
|
@ -39,23 +39,23 @@ func ServeAllTracks(app *model.AppState) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err = encoder.Encode(tracks)
|
err = encoder.Encode(tracks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve all tracks: %s\n", err)
|
fmt.Printf("WARN: Failed to serve all tracks: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeTrack(app *model.AppState, 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(app.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
releases := []string{}
|
releases := []string{}
|
||||||
|
@ -63,15 +63,15 @@ func ServeTrack(app *model.AppState, track *model.Track) http.Handler {
|
||||||
releases = append(releases, release.ID)
|
releases = append(releases, release.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err = encoder.Encode(Track{ track, releases })
|
err = encoder.Encode(Track{ track, releases })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve track %s: %s\n", track.ID, err)
|
fmt.Printf("WARN: Failed to serve 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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTrack(app *model.AppState) http.Handler {
|
func CreateTrack(app *model.AppState) http.Handler {
|
||||||
|
|
|
@ -1,56 +1,56 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleImageUpload(app *model.AppState, 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(app.Config.DataDirectory, directory)
|
directory = filepath.Join(app.Config.DataDirectory, directory)
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case "png":
|
case "png":
|
||||||
case "jpg":
|
case "jpg":
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg")
|
return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg")
|
||||||
}
|
}
|
||||||
filename = fmt.Sprintf("%s.%s", filename, ext)
|
filename = fmt.Sprintf("%s.%s", filename, ext)
|
||||||
|
|
||||||
// ensure directory exists
|
// ensure directory exists
|
||||||
os.MkdirAll(directory, os.ModePerm)
|
os.MkdirAll(directory, os.ModePerm)
|
||||||
|
|
||||||
imagePath := filepath.Join(directory, filename)
|
imagePath := filepath.Join(directory, filename)
|
||||||
file, err := os.Create(imagePath)
|
file, err := os.Create(imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// TODO: generate compressed versions of image (512x512?)
|
// TODO: generate compressed versions of image (512x512?)
|
||||||
|
|
||||||
buffer := bufio.NewWriter(file)
|
buffer := bufio.NewWriter(file)
|
||||||
_, err = buffer.Write(imageData)
|
_, err = buffer.Write(imageData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := buffer.Flush(); err != nil {
|
if err := buffer.Flush(); err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext)
|
app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext)
|
||||||
|
|
||||||
return filename, nil
|
return filename, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllAccounts(db *sqlx.DB) ([]model.Account, error) {
|
func GetAllAccounts(db *sqlx.DB) ([]model.Account, error) {
|
||||||
|
@ -112,24 +112,24 @@ func DeleteAccount(db *sqlx.DB, accountID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IncrementAccountFails(db *sqlx.DB, accountID string) (bool, error) {
|
func IncrementAccountFails(db *sqlx.DB, accountID string) (bool, error) {
|
||||||
failAttempts := 0
|
failAttempts := 0
|
||||||
err := db.Get(&failAttempts, "UPDATE account SET fail_attempts = fail_attempts + 1 WHERE id=$1 RETURNING fail_attempts", accountID)
|
err := db.Get(&failAttempts, "UPDATE account SET fail_attempts = fail_attempts + 1 WHERE id=$1 RETURNING fail_attempts", accountID)
|
||||||
if err != nil { return false, err }
|
if err != nil { return false, err }
|
||||||
locked := false
|
locked := false
|
||||||
if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS {
|
if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS {
|
||||||
err = LockAccount(db, accountID)
|
err = LockAccount(db, accountID)
|
||||||
if err != nil { return false, err }
|
if err != nil { return false, err }
|
||||||
locked = true
|
locked = true
|
||||||
}
|
}
|
||||||
return locked, err
|
return locked, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func LockAccount(db *sqlx.DB, accountID string) error {
|
func LockAccount(db *sqlx.DB, accountID string) error {
|
||||||
_, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID)
|
_, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnlockAccount(db *sqlx.DB, accountID string) error {
|
func UnlockAccount(db *sqlx.DB, accountID string) error {
|
||||||
_, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID)
|
_, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
|
||||||
func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) {
|
func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) {
|
||||||
var artist = model.Artist{}
|
var artist = model.Artist{}
|
||||||
|
|
||||||
err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
|
err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &artist, nil
|
return &artist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
||||||
var artists = []*model.Artist{}
|
var artists = []*model.Artist{}
|
||||||
|
|
||||||
err := db.Select(&artists, "SELECT * FROM artist")
|
err := db.Select(&artists, "SELECT * FROM artist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return artists, nil
|
return artists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) {
|
func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) {
|
||||||
var artists = []*model.Artist{}
|
var artists = []*model.Artist{}
|
||||||
|
|
||||||
err := db.Select(&artists,
|
err := db.Select(&artists,
|
||||||
"SELECT * FROM artist "+
|
"SELECT * FROM artist "+
|
||||||
"WHERE id NOT IN "+
|
"WHERE id NOT IN "+
|
||||||
"(SELECT artist FROM musiccredit WHERE release=$1)",
|
"(SELECT artist FROM musiccredit WHERE release=$1)",
|
||||||
releaseID)
|
releaseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return artists, nil
|
return artists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) {
|
func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) {
|
||||||
|
@ -54,9 +54,9 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.
|
||||||
if !show_hidden { query += "AND visible=true " }
|
if !show_hidden { query += "AND visible=true " }
|
||||||
query += "ORDER BY release_date DESC"
|
query += "ORDER BY release_date DESC"
|
||||||
rows, err := db.Query(query, artistID)
|
rows, err := db.Query(query, artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
type NamePrimary struct {
|
type NamePrimary struct {
|
||||||
|
@ -102,13 +102,13 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.
|
||||||
|
|
||||||
func CreateArtist(db *sqlx.DB, artist *model.Artist) error {
|
func CreateArtist(db *sqlx.DB, artist *model.Artist) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO artist (id, name, website, avatar) "+
|
"INSERT INTO artist (id, name, website, avatar) "+
|
||||||
"VALUES ($1, $2, $3, $4)",
|
"VALUES ($1, $2, $3, $4)",
|
||||||
artist.ID,
|
artist.ID,
|
||||||
artist.Name,
|
artist.Name,
|
||||||
artist.Website,
|
artist.Website,
|
||||||
artist.Avatar,
|
artist.Avatar,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetConfig() model.Config {
|
func GetConfig() model.Config {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var inviteChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
var inviteChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the request's original IP address, resolving the `x-forwarded-for`
|
// Returns the request's original IP address, resolving the `x-forwarded-for`
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DB_VERSION int = 4
|
const DB_VERSION int = 4
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
|
func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TOKEN_LEN = 64
|
const TOKEN_LEN = 64
|
||||||
|
@ -33,19 +33,19 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil {
|
if session != nil {
|
||||||
if session.UserAgent != r.UserAgent() {
|
if session.UserAgent != r.UserAgent() {
|
||||||
msg := "Session user agent mismatch. A cookie may have been hijacked!"
|
msg := "Session user agent mismatch. A cookie may have been hijacked!"
|
||||||
if session.Account != nil {
|
if session.Account != nil {
|
||||||
account, _ := GetAccountByID(app.DB, session.Account.ID)
|
account, _ := GetAccountByID(app.DB, session.Account.ID)
|
||||||
msg += " (Account \"" + account.Username + "\")"
|
msg += " (Account \"" + account.Username + "\")"
|
||||||
}
|
}
|
||||||
app.Log.Warn(log.TYPE_ACCOUNT, msg)
|
app.Log.Warn(log.TYPE_ACCOUNT, msg)
|
||||||
err = DeleteSession(app.DB, session.Token)
|
err = DeleteSession(app.DB, session.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session")
|
app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TOTP_SECRET_LENGTH = 32
|
const TOTP_SECRET_LENGTH = 32
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
@ -13,19 +13,19 @@ func GetTrack(db *sqlx.DB, id string) (*model.Track, error) {
|
||||||
|
|
||||||
stmt, _ := db.Preparex("SELECT * FROM musictrack WHERE id=$1")
|
stmt, _ := db.Preparex("SELECT * FROM musictrack WHERE id=$1")
|
||||||
err := stmt.Get(&track, id)
|
err := stmt.Get(&track, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &track, nil
|
return &track, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&tracks, "SELECT * FROM musictrack")
|
err := db.Select(&tracks, "SELECT * FROM musictrack")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
@ -33,33 +33,33 @@ func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)")
|
err := db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTracksNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Track, error) {
|
func GetTracksNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&tracks,
|
err := db.Select(&tracks,
|
||||||
"SELECT * FROM musictrack "+
|
"SELECT * FROM musictrack "+
|
||||||
"WHERE id NOT IN "+
|
"WHERE id NOT IN "+
|
||||||
"(SELECT track FROM musicreleasetrack WHERE release=$1)",
|
"(SELECT track FROM musicreleasetrack WHERE release=$1)",
|
||||||
releaseID)
|
releaseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, error) {
|
func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, error) {
|
||||||
var releases = []*model.Release{}
|
var releases = []*model.Release{}
|
||||||
|
|
||||||
err := db.Select(&releases,
|
err := db.Select(&releases,
|
||||||
"SELECT id,title,type,release_date,artwork,buylink "+
|
"SELECT id,title,type,release_date,artwork,buylink "+
|
||||||
"FROM musicrelease "+
|
"FROM musicrelease "+
|
||||||
"JOIN musicreleasetrack ON release=id "+
|
"JOIN musicreleasetrack ON release=id "+
|
||||||
|
@ -67,9 +67,9 @@ func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release,
|
||||||
"ORDER BY release_date",
|
"ORDER BY release_date",
|
||||||
trackID,
|
trackID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type NamePrimary struct {
|
type NamePrimary struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -114,14 +114,14 @@ func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release,
|
||||||
func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&tracks,
|
err := db.Select(&tracks,
|
||||||
"SELECT id, title, description, lyrics, preview_url FROM musictrack "+
|
"SELECT id, title, description, lyrics, preview_url FROM musictrack "+
|
||||||
"WHERE id NOT IN "+
|
"WHERE id NOT IN "+
|
||||||
"(SELECT track FROM musicreleasetrack)",
|
"(SELECT track FROM musicreleasetrack)",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package cursor
|
package cursor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CursorClient struct {
|
type CursorClient struct {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const API_ENDPOINT = "https://discord.com/api/v10"
|
const API_ENDPOINT = "https://discord.com/api/v10"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
52
main.go
52
main.go
|
@ -1,33 +1,33 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
stdLog "log"
|
stdLog "log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/admin"
|
"arimelody-web/admin"
|
||||||
"arimelody-web/api"
|
"arimelody-web/api"
|
||||||
"arimelody-web/colour"
|
"arimelody-web/colour"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/cursor"
|
"arimelody-web/cursor"
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/templates"
|
"arimelody-web/templates"
|
||||||
"arimelody-web/view"
|
"arimelody-web/view"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used for database migrations
|
// used for database migrations
|
||||||
|
@ -282,7 +282,7 @@ func main() {
|
||||||
account.ID,
|
account.ID,
|
||||||
email,
|
email,
|
||||||
account.CreatedAt,
|
account.CreatedAt,
|
||||||
account.Locked,
|
account.Locked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const COOKIE_TOKEN string = "AM_SESSION"
|
const COOKIE_TOKEN string = "AM_SESSION"
|
||||||
const MAX_LOGIN_FAIL_ATTEMPTS int = 3
|
const MAX_LOGIN_FAIL_ATTEMPTS int = 3
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Account struct {
|
Account struct {
|
||||||
ID string `json:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
Username string `json:"username" db:"username"`
|
Username string `json:"username" db:"username"`
|
||||||
Password string `json:"password" db:"password"`
|
Password string `json:"password" db:"password"`
|
||||||
Email sql.NullString `json:"email" db:"email"`
|
Email sql.NullString `json:"email" db:"email"`
|
||||||
AvatarURL sql.NullString `json:"avatar_url" db:"avatar_url"`
|
AvatarURL sql.NullString `json:"avatar_url" db:"avatar_url"`
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
FailAttempts int `json:"fail_attempts" db:"fail_attempts"`
|
FailAttempts int `json:"fail_attempts" db:"fail_attempts"`
|
||||||
Locked bool `json:"locked" db:"locked"`
|
Locked bool `json:"locked" db:"locked"`
|
||||||
|
|
||||||
Privileges []AccountPrivilege `json:"privileges"`
|
Privileges []AccountPrivilege `json:"privileges"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
"arimelody-web/log"
|
"arimelody-web/log"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Artist struct {
|
Artist struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (artist Artist) GetAvatar() string {
|
func (artist Artist) GetAvatar() string {
|
||||||
if artist.Avatar == "" {
|
if artist.Avatar == "" {
|
||||||
return "/img/default-avatar.png"
|
return "/img/default-avatar.png"
|
||||||
}
|
}
|
||||||
return artist.Avatar
|
return artist.Avatar
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (link Link) NormaliseName() string {
|
func (link Link) NormaliseName() string {
|
||||||
rgx := regexp.MustCompile(`[^a-z0-9\-]`)
|
rgx := regexp.MustCompile(`[^a-z0-9\-]`)
|
||||||
return rgx.ReplaceAllString(strings.ToLower(link.Name), "")
|
return rgx.ReplaceAllString(strings.ToLower(link.Name), "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -73,23 +73,23 @@ func (release Release) GetUniqueArtistNames(only_primary bool) []string {
|
||||||
names = append(names, credit.Artist.Name)
|
names = append(names, credit.Artist.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func (release Release) PrintArtists(only_primary bool, ampersand bool) string {
|
func (release Release) PrintArtists(only_primary bool, ampersand bool) string {
|
||||||
names := release.GetUniqueArtistNames(only_primary)
|
names := release.GetUniqueArtistNames(only_primary)
|
||||||
|
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return "Unknown Artist"
|
return "Unknown Artist"
|
||||||
} else if len(names) == 1 {
|
} else if len(names) == 1 {
|
||||||
return names[0]
|
return names[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if ampersand {
|
if ampersand {
|
||||||
res := strings.Join(names[:len(names)-1], ", ")
|
res := strings.Join(names[:len(names)-1], ", ")
|
||||||
res += " & " + names[len(names)-1]
|
res += " & " + names[len(names)-1]
|
||||||
return res
|
return res
|
||||||
} else {
|
} else {
|
||||||
return strings.Join(names[:], ", ")
|
return strings.Join(names[:], ", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Release_DescriptionHTML(t *testing.T) {
|
func Test_Release_DescriptionHTML(t *testing.T) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TOTP struct {
|
type TOTP struct {
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Track struct {
|
Track struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Lyrics string `json:"lyrics" db:"lyrics"`
|
Lyrics string `json:"lyrics" db:"lyrics"`
|
||||||
PreviewURL string `json:"previewURL" db:"preview_url"`
|
PreviewURL string `json:"previewURL" db:"preview_url"`
|
||||||
|
|
||||||
Number int
|
Number int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (track Track) GetDescriptionHTML() template.HTML {
|
func (track Track) GetDescriptionHTML() template.HTML {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var IndexTemplate = template.Must(template.ParseFiles(
|
var IndexTemplate = template.Must(template.ParseFiles(
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/templates"
|
"arimelody-web/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP HANDLER METHODS
|
// HTTP HANDLER METHODS
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue