diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index a882442..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,7 +0,0 @@
-root = true
-
-[*]
-end_of_line = lf
-insert_final_newline = true
-indent_style = space
-indent_size = 4
diff --git a/README.md b/README.md
index 464379e..e5df7f6 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,3 @@ need to be up for this, making this ideal for some offline maintenance.
- `purgeInvites`: Deletes all available invite codes.
- `listAccounts`: Lists all active accounts.
- `deleteAccount
{{.Title}}
- {{.ReleaseDate.Year}}
+ {{.GetReleaseYear}}
{{if not .Visible}}(hidden){{end}}
diff --git a/admin/http.go b/admin/http.go
index 245a152..b16c209 100644
--- a/admin/http.go
+++ b/admin/http.go
@@ -1,20 +1,20 @@
package admin
import (
- "context"
- "database/sql"
- "fmt"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
+ "context"
+ "database/sql"
+ "fmt"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
- "arimelody-web/controller"
- "arimelody-web/log"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/log"
+ "arimelody-web/model"
- "golang.org/x/crypto/bcrypt"
+ "golang.org/x/crypto/bcrypt"
)
func Handler(app *model.AppState) http.Handler {
@@ -274,20 +274,11 @@ func loginHandler(app *model.AppState) http.Handler {
render()
return
}
- if account.Locked {
- controller.SetSessionError(app.DB, session, "This account is locked.")
- render()
- return
- }
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password))
if err != nil {
app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" attempted login with incorrect password. (%s)", account.Username, controller.ResolveIP(app, r))
- if locked := handleFailedLogin(app, account, r); locked {
- controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
- } else {
- controller.SetSessionError(app.DB, session, "Invalid username or password.")
- }
+ controller.SetSessionError(app.DB, session, "Invalid username or password.")
render()
return
}
@@ -308,8 +299,6 @@ func loginHandler(app *model.AppState) http.Handler {
render()
return
}
- controller.SetSessionMessage(app.DB, session, "")
- controller.SetSessionError(app.DB, session, "")
http.Redirect(w, r, "/admin/totp", http.StatusFound)
return
}
@@ -388,14 +377,8 @@ func loginTOTPHandler(app *model.AppState) http.Handler {
return
}
if totpMethod == nil {
- 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, r); locked {
- controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.")
- controller.SetSessionAttemptAccount(app.DB, session, nil)
- http.Redirect(w, r, "/admin", http.StatusFound)
- } else {
- controller.SetSessionError(app.DB, session, "Incorrect TOTP.")
- }
+ app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" failed login (Invalid TOTP). (%s)", session.AttemptAccount.Username, controller.ResolveIP(app, r))
+ controller.SetSessionError(app.DB, session, "Invalid TOTP.")
render()
return
}
@@ -483,7 +466,7 @@ func staticHandler() http.Handler {
func enforceSession(app *model.AppState, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session, err := controller.GetSessionFromRequest(app, r)
+ session, err := controller.GetSessionFromRequest(app.DB, r)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -513,30 +496,3 @@ func enforceSession(app *model.AppState, next http.Handler) http.Handler {
next.ServeHTTP(w, r.WithContext(ctx))
})
}
-
-func handleFailedLogin(app *model.AppState, account *model.Account, r *http.Request) bool {
- locked, err := controller.IncrementAccountFails(app.DB, account.ID)
- if err != nil {
- fmt.Fprintf(
- os.Stderr,
- "WARN: Failed to increment login failures for \"%s\": %v\n",
- account.Username,
- err,
- )
- app.Log.Warn(
- log.TYPE_ACCOUNT,
- "Failed to increment login failures for \"%s\"",
- account.Username,
- )
- }
- if locked {
- app.Log.Warn(
- log.TYPE_ACCOUNT,
- "Account \"%s\" was locked: %d failed login attempts (IP: %s)",
- account.Username,
- model.MAX_LOGIN_FAIL_ATTEMPTS,
- controller.ResolveIP(app, r),
- )
- }
- return locked
-}
diff --git a/admin/logshttp.go b/admin/logshttp.go
index 7249b16..93dc5b7 100644
--- a/admin/logshttp.go
+++ b/admin/logshttp.go
@@ -1,12 +1,12 @@
package admin
import (
- "arimelody-web/log"
- "arimelody-web/model"
- "fmt"
- "net/http"
- "os"
- "strings"
+ "arimelody-web/log"
+ "arimelody-web/model"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
)
func logsHandler(app *model.AppState) http.Handler {
diff --git a/admin/releasehttp.go b/admin/releasehttp.go
index c6b68ab..7ef4d37 100644
--- a/admin/releasehttp.go
+++ b/admin/releasehttp.go
@@ -1,12 +1,12 @@
package admin
import (
- "fmt"
- "net/http"
- "strings"
+ "fmt"
+ "net/http"
+ "strings"
- "arimelody-web/controller"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/model"
)
func serveRelease(app *model.AppState) http.Handler {
diff --git a/admin/templates.go b/admin/templates.go
index 606d569..12cdf08 100644
--- a/admin/templates.go
+++ b/admin/templates.go
@@ -1,12 +1,12 @@
package admin
import (
- "arimelody-web/log"
- "fmt"
- "html/template"
- "path/filepath"
- "strings"
- "time"
+ "arimelody-web/log"
+ "fmt"
+ "html/template"
+ "path/filepath"
+ "strings"
+ "time"
)
var indexTemplate = template.Must(template.ParseFiles(
diff --git a/admin/trackhttp.go b/admin/trackhttp.go
index 93eacdb..a92f81a 100644
--- a/admin/trackhttp.go
+++ b/admin/trackhttp.go
@@ -1,9 +1,9 @@
package admin
import (
- "fmt"
- "net/http"
- "strings"
+ "fmt"
+ "net/http"
+ "strings"
"arimelody-web/model"
"arimelody-web/controller"
diff --git a/api/api.go b/api/api.go
index 398db4b..d3c83ce 100644
--- a/api/api.go
+++ b/api/api.go
@@ -1,15 +1,15 @@
package api
import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
- "arimelody-web/controller"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/model"
)
func Handler(app *model.AppState) http.Handler {
diff --git a/api/artist.go b/api/artist.go
index 01899a6..9006cc3 100644
--- a/api/artist.go
+++ b/api/artist.go
@@ -1,66 +1,66 @@
package api
import (
- "encoding/json"
- "fmt"
- "io/fs"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
+ "encoding/json"
+ "fmt"
+ "io/fs"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
- "arimelody-web/controller"
- "arimelody-web/log"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/log"
+ "arimelody-web/model"
)
func ServeAllArtists(app *model.AppState) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var artists = []*model.Artist{}
artists, err := controller.GetAllArtists(app.DB)
- if err != nil {
+ if err != nil {
fmt.Printf("WARN: Failed to serve all artists: %s\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
- w.Header().Add("Content-Type", "application/json")
+ w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", "\t")
err = encoder.Encode(artists)
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
+ if err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ }
})
}
func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- type (
- creditJSON struct {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ type (
+ creditJSON struct {
ID string `json:"id"`
Title string `json:"title"`
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
Artwork string `json:"artwork"`
- Role string `json:"role"`
- Primary bool `json:"primary"`
- }
- artistJSON struct {
- *model.Artist
- Credits map[string]creditJSON `json:"credits"`
- }
- )
+ Role string `json:"role"`
+ Primary bool `json:"primary"`
+ }
+ artistJSON struct {
+ *model.Artist
+ Credits map[string]creditJSON `json:"credits"`
+ }
+ )
session := r.Context().Value("session").(*model.Session)
show_hidden_releases := session != nil && session.Account != nil
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)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
var credits = map[string]creditJSON{}
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.SetIndent("", "\t")
err = encoder.Encode(artistJSON{
Artist: artist,
Credits: credits,
})
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- })
+ if err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ }
+ })
}
func CreateArtist(app *model.AppState) http.Handler {
diff --git a/api/release.go b/api/release.go
index e07f0d7..efed8dd 100644
--- a/api/release.go
+++ b/api/release.go
@@ -1,26 +1,26 @@
package api
import (
- "encoding/json"
- "fmt"
- "io/fs"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
+ "encoding/json"
+ "fmt"
+ "io/fs"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
- "arimelody-web/controller"
- "arimelody-web/log"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/log"
+ "arimelody-web/model"
)
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
privileged := false
if !release.Visible {
- session, err := controller.GetSessionFromRequest(app, r)
+ session, err := controller.GetSessionFromRequest(app.DB, r)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@@ -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.SetIndent("", "\t")
err := encoder.Encode(response)
- if err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- })
+ if err != nil {
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ })
}
func ServeCatalog(app *model.AppState) http.Handler {
diff --git a/api/track.go b/api/track.go
index 4e48418..e7d7c07 100644
--- a/api/track.go
+++ b/api/track.go
@@ -1,13 +1,13 @@
package api
import (
- "encoding/json"
- "fmt"
- "net/http"
+ "encoding/json"
+ "fmt"
+ "net/http"
- "arimelody-web/controller"
- "arimelody-web/log"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/log"
+ "arimelody-web/model"
)
type (
@@ -29,7 +29,7 @@ func ServeAllTracks(app *model.AppState) http.Handler {
dbTracks, err := controller.GetAllTracks(app.DB)
if err != nil {
fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
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.SetIndent("", "\t")
err = encoder.Encode(tracks)
- if err != nil {
+ if err != nil {
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 {
- 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)
if err != nil {
fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
releases := []string{}
@@ -63,15 +63,15 @@ func ServeTrack(app *model.AppState, track *model.Track) http.Handler {
releases = append(releases, release.ID)
}
- w.Header().Add("Content-Type", "application/json")
+ w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", "\t")
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)
- 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 {
diff --git a/api/uploads.go b/api/uploads.go
index 4678f22..60ab7dd 100644
--- a/api/uploads.go
+++ b/api/uploads.go
@@ -1,56 +1,56 @@
package api
import (
- "arimelody-web/log"
- "arimelody-web/model"
- "bufio"
- "encoding/base64"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strings"
+ "arimelody-web/log"
+ "arimelody-web/model"
+ "bufio"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
)
func HandleImageUpload(app *model.AppState, data *string, directory string, filename string) (string, error) {
- split := strings.Split(*data, ";base64,")
- header := split[0]
- imageData, err := base64.StdEncoding.DecodeString(split[1])
- ext, _ := strings.CutPrefix(header, "data:image/")
+ split := strings.Split(*data, ";base64,")
+ header := split[0]
+ imageData, err := base64.StdEncoding.DecodeString(split[1])
+ ext, _ := strings.CutPrefix(header, "data:image/")
directory = filepath.Join(app.Config.DataDirectory, directory)
- switch ext {
- case "png":
- case "jpg":
- case "jpeg":
- default:
- return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg")
- }
+ switch ext {
+ case "png":
+ case "jpg":
+ case "jpeg":
+ default:
+ return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg")
+ }
filename = fmt.Sprintf("%s.%s", filename, ext)
- // ensure directory exists
- os.MkdirAll(directory, os.ModePerm)
+ // ensure directory exists
+ os.MkdirAll(directory, os.ModePerm)
- imagePath := filepath.Join(directory, filename)
- file, err := os.Create(imagePath)
- if err != nil {
- return "", err
- }
- defer file.Close()
+ imagePath := filepath.Join(directory, filename)
+ file, err := os.Create(imagePath)
+ if err != nil {
+ return "", err
+ }
+ defer file.Close()
// TODO: generate compressed versions of image (512x512?)
- buffer := bufio.NewWriter(file)
- _, err = buffer.Write(imageData)
- if err != nil {
+ buffer := bufio.NewWriter(file)
+ _, err = buffer.Write(imageData)
+ if err != nil {
return "", nil
- }
+ }
- if err := buffer.Flush(); err != nil {
+ if err := buffer.Flush(); err != nil {
return "", nil
- }
+ }
app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext)
- return filename, nil
+ return filename, nil
}
diff --git a/controller/account.go b/controller/account.go
index ab64ca5..9c7c1e1 100644
--- a/controller/account.go
+++ b/controller/account.go
@@ -1,10 +1,10 @@
package controller
import (
- "arimelody-web/model"
- "strings"
+ "arimelody-web/model"
+ "strings"
- "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx"
)
func GetAllAccounts(db *sqlx.DB) ([]model.Account, error) {
@@ -110,26 +110,3 @@ func DeleteAccount(db *sqlx.DB, accountID string) error {
_, err := db.Exec("DELETE FROM account WHERE id=$1", accountID)
return err
}
-
-func IncrementAccountFails(db *sqlx.DB, accountID string) (bool, error) {
- failAttempts := 0
- 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 }
- locked := false
- if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS {
- err = LockAccount(db, accountID)
- if err != nil { return false, err }
- locked = true
- }
- return locked, err
-}
-
-func LockAccount(db *sqlx.DB, accountID string) error {
- _, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID)
- return err
-}
-
-func UnlockAccount(db *sqlx.DB, accountID string) error {
- _, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID)
- return err
-}
diff --git a/controller/artist.go b/controller/artist.go
index f086778..1a613aa 100644
--- a/controller/artist.go
+++ b/controller/artist.go
@@ -1,48 +1,48 @@
package controller
import (
- "arimelody-web/model"
+ "arimelody-web/model"
- "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx"
)
// DATABASE
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)
- if err != nil {
- return nil, err
- }
+ err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
+ if err != nil {
+ return nil, err
+ }
- return &artist, nil
+ return &artist, nil
}
func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
- var artists = []*model.Artist{}
+ var artists = []*model.Artist{}
- err := db.Select(&artists, "SELECT * FROM artist")
- if err != nil {
- return nil, err
- }
+ err := db.Select(&artists, "SELECT * FROM artist")
+ if err != nil {
+ return nil, err
+ }
- return artists, nil
+ return artists, nil
}
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 "+
"WHERE id NOT IN "+
"(SELECT artist FROM musiccredit WHERE release=$1)",
releaseID)
- if err != nil {
- return nil, err
- }
+ if err != nil {
+ return nil, err
+ }
- return artists, nil
+ return artists, nil
}
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 " }
query += "ORDER BY release_date DESC"
rows, err := db.Query(query, artistID)
- if err != nil {
- return nil, err
- }
+ if err != nil {
+ return nil, err
+ }
defer rows.Close()
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 {
_, err := db.Exec(
- "INSERT INTO artist (id, name, website, avatar) "+
+ "INSERT INTO artist (id, name, website, avatar) "+
"VALUES ($1, $2, $3, $4)",
- artist.ID,
- artist.Name,
- artist.Website,
+ artist.ID,
+ artist.Name,
+ artist.Website,
artist.Avatar,
- )
+ )
if err != nil {
return err
}
diff --git a/controller/config.go b/controller/config.go
index fdfa756..5a69d49 100644
--- a/controller/config.go
+++ b/controller/config.go
@@ -1,14 +1,14 @@
package controller
import (
- "errors"
- "fmt"
- "os"
- "strconv"
+ "errors"
+ "fmt"
+ "os"
+ "strconv"
- "arimelody-web/model"
+ "arimelody-web/model"
- "github.com/pelletier/go-toml/v2"
+ "github.com/pelletier/go-toml/v2"
)
func GetConfig() model.Config {
@@ -77,9 +77,5 @@ func handleConfigOverrides(config *model.Config) error {
if env, has := os.LookupEnv("ARIMELODY_DISCORD_CLIENT_ID"); has { config.Discord.ClientID = env }
if env, has := os.LookupEnv("ARIMELODY_DISCORD_SECRET"); has { config.Discord.Secret = env }
- if env, has := os.LookupEnv("ARIMELODY_TWITCH_BROADCASTER"); has { config.Twitch.Broadcaster = env }
- if env, has := os.LookupEnv("ARIMELODY_TWITCH_CLIENT_ID"); has { config.Twitch.ClientID = env }
- if env, has := os.LookupEnv("ARIMELODY_TWITCH_SECRET"); has { config.Twitch.Secret = env }
-
return nil
}
diff --git a/controller/invite.go b/controller/invite.go
index a7bde40..f30db64 100644
--- a/controller/invite.go
+++ b/controller/invite.go
@@ -1,12 +1,12 @@
package controller
import (
- "arimelody-web/model"
- "math/rand"
- "strings"
- "time"
+ "arimelody-web/model"
+ "math/rand"
+ "strings"
+ "time"
- "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx"
)
var inviteChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
diff --git a/controller/ip.go b/controller/ip.go
index cbc3054..233d76a 100644
--- a/controller/ip.go
+++ b/controller/ip.go
@@ -1,10 +1,10 @@
package controller
import (
- "arimelody-web/model"
- "net/http"
- "slices"
- "strings"
+ "arimelody-web/model"
+ "net/http"
+ "slices"
+ "strings"
)
// Returns the request's original IP address, resolving the `x-forwarded-for`
diff --git a/controller/migrator.go b/controller/migrator.go
index 4b99b9c..b053a27 100644
--- a/controller/migrator.go
+++ b/controller/migrator.go
@@ -1,14 +1,14 @@
package controller
import (
- "fmt"
- "os"
- "time"
+ "fmt"
+ "os"
+ "time"
- "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx"
)
-const DB_VERSION int = 4
+const DB_VERSION int = 3
func CheckDBVersionAndMigrate(db *sqlx.DB) {
db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody")
@@ -45,10 +45,6 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) {
ApplyMigration(db, "002-audit-logs")
oldDBVersion = 3
- case 3:
- ApplyMigration(db, "003-fail-lock")
- oldDBVersion = 4
-
}
}
diff --git a/controller/qr.go b/controller/qr.go
index dd08637..7ada0f8 100644
--- a/controller/qr.go
+++ b/controller/qr.go
@@ -1,9 +1,13 @@
package controller
import (
- "encoding/base64"
- "image"
- "image/color"
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "image"
+ "image/color"
+ "image/png"
"github.com/skip2/go-qrcode"
)
@@ -29,6 +33,69 @@ const (
HIGH
)
+func noDepsGenerateQRCode() (string, error) {
+ version := 1
+
+ size := 0
+ size = 21 + version * 4
+ if version > 10 {
+ return "", errors.New(fmt.Sprintf("QR version %d not supported", version))
+ }
+
+ img := image.NewGray(image.Rect(0, 0, size + margin * 2, size + margin * 2))
+
+ // fill white
+ for y := range size + margin * 2 {
+ for x := range size + margin * 2 {
+ img.Set(x, y, color.White)
+ }
+ }
+
+ // draw alignment squares
+ drawLargeAlignmentSquare(margin, margin, img)
+ drawLargeAlignmentSquare(margin, margin + size - 7, img)
+ drawLargeAlignmentSquare(margin + size - 7, margin, img)
+ drawSmallAlignmentSquare(size - 5, size - 5, img)
+ /*
+ if version > 4 {
+ space := version * 3 - 2
+ end := size / space
+ for y := range size / space + 1 {
+ for x := range size / space + 1 {
+ if x == 0 && y == 0 { continue }
+ if x == 0 && y == end { continue }
+ if x == end && y == 0 { continue }
+ if x == end && y == end { continue }
+ drawSmallAlignmentSquare(
+ x * space + margin + 4,
+ y * space + margin + 4,
+ img,
+ )
+ }
+ }
+ }
+ */
+
+ // draw timing bits
+ for i := margin + 6; i < size - 4; i++ {
+ if (i % 2 == 0) {
+ img.Set(i, margin + 6, color.Black)
+ img.Set(margin + 6, i, color.Black)
+ }
+ }
+ img.Set(margin + 8, size - 4, color.Black)
+
+ var imgBuf bytes.Buffer
+ err := png.Encode(&imgBuf, img)
+ if err != nil {
+ return "", err
+ }
+
+ base64Img := base64.StdEncoding.EncodeToString(imgBuf.Bytes())
+
+ return "data:image/png;base64," + base64Img, nil
+}
+
func drawLargeAlignmentSquare(x int, y int, img *image.Gray) {
for yi := range 7 {
for xi := range 7 {
diff --git a/controller/release.go b/controller/release.go
index 3dcad26..362669a 100644
--- a/controller/release.go
+++ b/controller/release.go
@@ -1,12 +1,12 @@
package controller
import (
- "errors"
- "fmt"
+ "errors"
+ "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) {
diff --git a/controller/session.go b/controller/session.go
index 5028789..cf423fe 100644
--- a/controller/session.go
+++ b/controller/session.go
@@ -1,22 +1,21 @@
package controller
import (
- "database/sql"
- "errors"
- "fmt"
- "net/http"
- "strings"
- "time"
+ "database/sql"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
- "arimelody-web/log"
- "arimelody-web/model"
+ "arimelody-web/model"
- "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx"
)
const TOKEN_LEN = 64
-func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session, error) {
+func GetSessionFromRequest(db *sqlx.DB, r *http.Request) (*model.Session, error) {
sessionCookie, err := r.Cookie(model.COOKIE_TOKEN)
if err != nil && err != http.ErrNoCookie {
return nil, errors.New(fmt.Sprintf("Failed to retrieve session cookie: %v", err))
@@ -26,26 +25,14 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session
if sessionCookie != nil {
// fetch existing session
- session, err = GetSession(app.DB, sessionCookie.Value)
+ session, err = GetSession(db, sessionCookie.Value)
if err != nil && !strings.Contains(err.Error(), "no rows") {
return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v", err))
}
if session != nil {
- if session.UserAgent != r.UserAgent() {
- msg := "Session user agent mismatch. A cookie may have been hijacked!"
- if session.Account != nil {
- account, _ := GetAccountByID(app.DB, session.Account.ID)
- msg += " (Account \"" + account.Username + "\")"
- }
- app.Log.Warn(log.TYPE_ACCOUNT, msg)
- err = DeleteSession(app.DB, session.Token)
- if err != nil {
- app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session")
- }
- return nil, nil
- }
+ // TODO: consider running security checks here (i.e. user agent mismatches)
}
}
@@ -188,7 +175,3 @@ func DeleteSession(db *sqlx.DB, token string) error {
return err
}
-func DeleteExpiredSessions(db *sqlx.DB) error {
- _, err := db.Exec("DELETE FROM session WHERE expires_at
a test
description!"
- got := release.GetDescriptionHTML()
- if want != string(got) {
- t.Errorf(`release description incorrectly formatted (want "%s", got "%s")`, want, got)
- }
-}
-
-func Test_Release_ReleaseDate(t *testing.T) {
- release := Release{
- ReleaseDate: time.Date(2025, time.July, 26, 16, 0, 0, 0, time.UTC),
- }
-
- want := "2025-07-26T16:00"
- got := release.TextReleaseDate()
- if want != got {
- t.Errorf(`release date incorrectly formatted (want "%s", got "%s")`, want, got)
- }
-
- want = "26 July 2025"
- got = release.PrintReleaseDate()
- if want != got {
- t.Errorf(`release date (print) incorrectly formatted (want "%s", got "%s")`, want, got)
- }
-}
-
-func Test_Release_Artwork(t *testing.T) {
- want := "testartwork.png"
- release := Release{ Artwork: want }
-
- got := release.GetArtwork()
- if want != got {
- t.Errorf(`correct value not returned when artwork is populated (want "%s", got "%s")`, want, got)
- }
-
- release = Release{}
-
- want = "/img/default-cover-art.png"
- got = release.GetArtwork()
- if want != got {
- t.Errorf(`default value not returned when artwork is empty (want "%s", got "%s")`, want, got)
- }
-}
-
-func Test_Release_IsSingle(t *testing.T) {
- release := Release{
- Tracks: []*Track{},
- }
-
- if release.IsSingle() {
- t.Errorf("IsSingle() == true when no tracks are present")
- }
-
- release.Tracks = append(release.Tracks, &Track{})
- if !release.IsSingle() {
- t.Errorf("IsSingle() == false when one track is present")
- }
-
- release.Tracks = append(release.Tracks, &Track{})
- if release.IsSingle() {
- t.Errorf("IsSingle() == true when >1 tracks are present")
- }
-}
-
-func Test_Release_IsReleased(t *testing.T) {
- release := Release {
- ReleaseDate: time.Now(),
- }
-
- if !release.IsReleased() {
- t.Errorf("IsRelease() == false when release date in the past")
- }
-
- release.ReleaseDate = time.Now().Add(time.Hour)
- if release.IsReleased() {
- t.Errorf("IsRelease() == true when release date in the future")
- }
-}
-
-func Test_Release_PrintArtists(t *testing.T) {
- artist1 := "ari melody"
- artist2 := "aridoodle"
- artist3 := "idk"
- artist4 := "guest"
-
- release := Release {
- Credits: []*Credit{
- { Artist: Artist{ Name: artist1 }, Primary: true },
- { Artist: Artist{ Name: artist2 }, Primary: true },
- { Artist: Artist{ Name: artist3 }, Primary: false },
- { Artist: Artist{ Name: artist4 }, Primary: true },
- },
- }
-
- {
- want := []string{ artist1, artist2, artist4 }
- got := release.GetUniqueArtistNames(true)
- if len(want) != len(got) {
- t.Errorf(`len(GetUniqueArtistNames) (primary only) == %d, want %d`, len(got), len(want))
- }
- for i := range got {
- if want[i] != got[i] {
- t.Errorf(`GetUniqueArtistNames[%d] (primary only) == %s, want %s`, i, got[i], want[i])
- }
- }
-
- want = []string{ artist1, artist2, artist3, artist4 }
- got = release.GetUniqueArtistNames(false)
- if len(want) != len(got) {
- t.Errorf(`len(GetUniqueArtistNames) == %d, want %d`, len(got), len(want))
- }
- for i := range got {
- if want[i] != got[i] {
- t.Errorf(`GetUniqueArtistNames[%d] == %s, want %s`, i, got[i], want[i])
- }
- }
- }
-
- {
- want := "ari melody, aridoodle & guest"
- got := release.PrintArtists(true, true)
- if want != got {
- t.Errorf(`PrintArtists (primary only, ampersand) == "%s", want "%s"`, want, got)
- }
-
- want = "ari melody, aridoodle, guest"
- got = release.PrintArtists(true, false)
- if want != got {
- t.Errorf(`PrintArtists (primary only) == "%s", want "%s"`, want, got)
- }
-
- want = "ari melody, aridoodle, idk & guest"
- got = release.PrintArtists(false, true)
- if want != got {
- t.Errorf(`PrintArtists (all, ampersand) == "%s", want "%s"`, want, got)
- }
-
- want = "ari melody, aridoodle, idk, guest"
- got = release.PrintArtists(false, false)
- if want != got {
- t.Errorf(`PrintArtists (all) == "%s", want "%s"`, want, got)
- }
- }
-}
diff --git a/model/session.go b/model/session.go
index 7382de3..de016e1 100644
--- a/model/session.go
+++ b/model/session.go
@@ -1,8 +1,8 @@
package model
import (
- "database/sql"
- "time"
+ "database/sql"
+ "time"
)
type Session struct {
diff --git a/model/totp.go b/model/totp.go
index 108dae5..cfad10a 100644
--- a/model/totp.go
+++ b/model/totp.go
@@ -1,7 +1,7 @@
package model
import (
- "time"
+ "time"
)
type TOTP struct {
diff --git a/model/track.go b/model/track.go
index deaf086..ca54ddd 100644
--- a/model/track.go
+++ b/model/track.go
@@ -1,20 +1,20 @@
package model
import (
- "html/template"
- "strings"
+ "html/template"
+ "strings"
)
type (
- Track struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
+ Track struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
Lyrics string `json:"lyrics" db:"lyrics"`
- PreviewURL string `json:"previewURL" db:"preview_url"`
+ PreviewURL string `json:"previewURL" db:"preview_url"`
Number int
- }
+ }
)
func (track Track) GetDescriptionHTML() template.HTML {
diff --git a/model/track_test.go b/model/track_test.go
deleted file mode 100644
index fd500d7..0000000
--- a/model/track_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package model
-
-import (
- "testing"
-)
-
-func Test_Track_DescriptionHTML(t *testing.T) {
- track := Track{
- Description: "this is\na test\ndescription!",
- }
-
- // descriptions are set by privileged users,
- // so we'll allow HTML injection here
- want := "this is
a test
description!"
- got := track.GetDescriptionHTML()
- if want != string(got) {
- t.Errorf(`track description incorrectly formatted (want "%s", got "%s")`, want, got)
- }
-}
-
-func Test_Track_LyricsHTML(t *testing.T) {
- track := Track{
- Lyrics: "these are\ntest\nlyrics!",
- }
-
- // lyrics are set by privileged users,
- // so we'll allow HTML injection here
- want := "these are
test
lyrics!"
- got := track.GetLyricsHTML()
- if want != string(got) {
- t.Errorf(`track lyrics incorrectly formatted (want "%s", got "%s")`, want, got)
- }
-}
-
-func Test_Track_Add(t *testing.T) {
- track := Track{}
-
- want := 4
- got := track.Add(2, 2)
- if want != got {
- t.Errorf(`somehow, we screwed up addition. (want %d, got %d)`, want, got)
- }
-}
diff --git a/model/twitch.go b/model/twitch.go
deleted file mode 100644
index 6bca17d..0000000
--- a/model/twitch.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package model
-
-import (
- "fmt"
- "strings"
- "time"
-)
-
-type (
- TwitchOAuthToken struct {
- AccessToken string
- ExpiresAt time.Time
- TokenType string
- }
-
- TwitchState struct {
- Token *TwitchOAuthToken
- }
-
- TwitchStreamInfo struct {
- ID string `json:"id"`
- UserID string `json:"user_id"`
- UserLogin string `json:"user_login"`
- UserName string `json:"user_name"`
- GameID string `json:"game_id"`
- GameName string `json:"game_name"`
- Type string `json:"type"`
- Title string `json:"title"`
- ViewerCount int `json:"viewer_count"`
- StartedAt string `json:"started_at"`
- Language string `json:"language"`
- ThumbnailURL string `json:"thumbnail_url"`
- TagIDs []string `json:"tag_ids"`
- Tags []string `json:"tags"`
- IsMature bool `json:"is_mature"`
- }
-)
-
-func (info *TwitchStreamInfo) Thumbnail(width int, height int) string {
- res := strings.Replace(info.ThumbnailURL, "{width}", fmt.Sprintf("%d", width), 1)
- res = strings.Replace(res, "{height}", fmt.Sprintf("%d", height), 1)
- return res
-}
diff --git a/public/img/brand/bandcamp.svg b/public/img/brand/bandcamp.svg
deleted file mode 100644
index 9623ec2..0000000
--- a/public/img/brand/bandcamp.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/public/img/brand/bluesky.svg b/public/img/brand/bluesky.svg
deleted file mode 100644
index d77fafe..0000000
--- a/public/img/brand/bluesky.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/public/img/brand/codeberg.svg b/public/img/brand/codeberg.svg
deleted file mode 100644
index 028b729..0000000
--- a/public/img/brand/codeberg.svg
+++ /dev/null
@@ -1,164 +0,0 @@
-
-
diff --git a/public/img/brand/discord.svg b/public/img/brand/discord.svg
deleted file mode 100644
index b636d15..0000000
--- a/public/img/brand/discord.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/img/brand/twitch.svg b/public/img/brand/twitch.svg
deleted file mode 100644
index 3120fea..0000000
--- a/public/img/brand/twitch.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
diff --git a/public/img/brand/youtube.svg b/public/img/brand/youtube.svg
deleted file mode 100644
index 3286071..0000000
--- a/public/img/brand/youtube.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/public/img/buttons/girlonthemoon.png b/public/img/buttons/girlonthemoon.png
deleted file mode 100644
index 4e98a12..0000000
Binary files a/public/img/buttons/girlonthemoon.png and /dev/null differ
diff --git a/public/img/buttons/thermia.gif b/public/img/buttons/thermia.gif
deleted file mode 100644
index a7ee70a..0000000
Binary files a/public/img/buttons/thermia.gif and /dev/null differ
diff --git a/public/script/config.js b/public/script/config.js
index 402a74b..1ab8b5a 100644
--- a/public/script/config.js
+++ b/public/script/config.js
@@ -55,7 +55,6 @@ class Config {
get crt() { return this._crt }
set crt(/** @type boolean */ enabled) {
this._crt = enabled;
- this.save();
if (enabled) {
document.body.classList.add("crt");
@@ -67,24 +66,26 @@ class Config {
this.#listeners.get('crt').forEach(callback => {
callback(this._crt);
})
+
+ this.save();
}
get cursor() { return this._cursor }
set cursor(/** @type boolean */ value) {
this._cursor = value;
- this.save();
this.#listeners.get('cursor').forEach(callback => {
callback(this._cursor);
})
+ this.save();
}
get cursorFunMode() { return this._cursorFunMode }
set cursorFunMode(/** @type boolean */ value) {
this._cursorFunMode = value;
- this.save();
this.#listeners.get('cursorFunMode').forEach(callback => {
callback(this._cursorFunMode);
})
+ this.save();
}
}
diff --git a/public/script/cursor.js b/public/script/cursor.js
index ff87068..188cdbb 100644
--- a/public/script/cursor.js
+++ b/public/script/cursor.js
@@ -328,7 +328,7 @@ function cursorSetup() {
switch (args[0]) {
case 'id': {
- myCursor.id = id;
+ myCursor.id = Number(args[1]);
break;
}
case 'join': {
@@ -381,12 +381,12 @@ function cursorDestroy() {
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('keypress', handleKeyPress);
document.removeEventListener('keyup', handleKeyUp);
-
- ctx.clearRect(0, 0, canvas.width, canvas.height);
cursors.clear();
myCursor = null;
+ cursorContainer.remove();
+
console.log(`Cursor no longer tracking.`);
running = false;
}
diff --git a/public/script/index.js b/public/script/index.js
index 2197bd4..512ed8f 100644
--- a/public/script/index.js
+++ b/public/script/index.js
@@ -1,5 +1,3 @@
-import { hijackClickEvent } from "./main.js";
-
const hexPrimary = document.getElementById("hex-primary");
const hexSecondary = document.getElementById("hex-secondary");
const hexTertiary = document.getElementById("hex-tertiary");
@@ -16,8 +14,3 @@ updateHexColours();
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
updateHexColours();
});
-
-document.querySelectorAll("ul#projects li.project-item").forEach(projectItem => {
- const link = projectItem.querySelector('a');
- hijackClickEvent(projectItem, link);
-});
diff --git a/public/script/main.js b/public/script/main.js
index 19eddc2..c1d5101 100644
--- a/public/script/main.js
+++ b/public/script/main.js
@@ -45,23 +45,6 @@ function fill_list(list) {
});
}
-export function hijackClickEvent(container, link) {
- container.addEventListener('click', event => {
- if (event.target.tagName.toLowerCase() === 'a') return;
- event.preventDefault();
- link.dispatchEvent(new MouseEvent('click', {
- bubbles: true,
- cancelable: true,
- view: window,
- ctrlKey: event.ctrlKey,
- metaKey: event.metaKey,
- shiftKey: event.shiftKey,
- altKey: event.altKey,
- button: event.button,
- }));
- });
-}
-
document.addEventListener("DOMContentLoaded", () => {
[...document.querySelectorAll(".typeout")]
.filter((e) => e.innerText != "")
diff --git a/public/script/music.js b/public/script/music.js
index 91f34ab..273ce2b 100644
--- a/public/script/music.js
+++ b/public/script/music.js
@@ -1,6 +1,12 @@
-import { hijackClickEvent } from "./main.js";
+import "./main.js";
document.querySelectorAll("div.music").forEach(container => {
- const link = container.querySelector(".music-title a")
- hijackClickEvent(container, link);
+ const link = container.querySelector(".music-title a").href
+
+ container.addEventListener("click", event => {
+ if (event.target.href) return;
+
+ event.preventDefault();
+ location = link;
+ });
});
diff --git a/public/style/colours.css b/public/style/colours.css
index de63198..2bf607d 100644
--- a/public/style/colours.css
+++ b/public/style/colours.css
@@ -6,7 +6,6 @@
--secondary: #f8e05b;
--tertiary: #f788fe;
--links: #5eb2ff;
- --live: #fd3737;
}
@media (prefers-color-scheme: light) {
diff --git a/public/style/header.css b/public/style/header.css
index f399d4d..48971b5 100644
--- a/public/style/header.css
+++ b/public/style/header.css
@@ -25,6 +25,7 @@ nav {
flex-grow: 1;
display: flex;
gap: .5em;
+ cursor: pointer;
}
img#header-icon {
@@ -35,19 +36,19 @@ img#header-icon {
}
#header-text {
+ width: 11em;
display: flex;
flex-direction: column;
justify-content: center;
+ flex-grow: 1;
}
#header-text h1 {
- width: fit-content;
margin: 0;
font-size: 1em;
}
#header-text h2 {
- width: fit-content;
height: 1.2em;
line-height: 1.2em;
margin: 0;
@@ -153,7 +154,7 @@ header ul li a:hover {
flex-direction: column;
gap: 1rem;
border-bottom: 1px solid #888;
- background: var(--background);
+ background: #080808;
display: none;
}
diff --git a/public/style/index.css b/public/style/index.css
index b970c17..f3cb761 100644
--- a/public/style/index.css
+++ b/public/style/index.css
@@ -96,36 +96,30 @@ hr {
overflow: visible;
}
-ul.platform-links {
- padding-left: 1em;
+ul.links {
display: flex;
- gap: .5em;
+ gap: 1em .5em;
flex-wrap: wrap;
}
-ul.platform-links li {
+ul.links li {
list-style: none;
}
-ul.platform-links li a {
+ul.links li a {
padding: .4em .5em;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- gap: .5em;
border: 1px solid var(--links);
color: var(--links);
border-radius: 2px;
background-color: transparent;
- transition-property: color, border-color, background-color, box-shadow;
+ transition-property: color, border-color, background-color;
transition-duration: .2s;
animation-delay: 0s;
animation: list-item-fadein .2s forwards;
opacity: 0;
}
-ul.platform-links li a:hover {
+ul.links li a:hover {
color: #eee;
border-color: #eee;
background-color: var(--links) !important;
@@ -133,75 +127,6 @@ ul.platform-links li a:hover {
box-shadow: 0 0 1em var(--links);
}
-ul.platform-links li a img {
- height: 1em;
- width: 1em;
-}
-
-ul#projects {
- padding: 0;
- list-style: none;
-}
-
-li.project-item {
- padding: .5em;
- border: 1px solid var(--links);
- margin: 1em 0;
- display: flex;
- flex-direction: row;
- gap: .5em;
- border-radius: 2px;
- transition-property: color, border-color, background-color, box-shadow;
- transition-duration: .2s;
- cursor: pointer;
-}
-li.project-item a {
- transition: color .2s linear;
-}
-
-li.project-item:hover {
- color: #eee;
- border-color: #eee;
- background-color: var(--links) !important;
- text-decoration: none;
- box-shadow: 0 0 1em var(--links);
-}
-li.project-item:hover a {
- color: #eee;
-}
-
-li.project-item .project-info {
- display: flex;
- flex-direction: column;
- justify-content: center;
-}
-
-li.project-item img.project-icon {
- width: 2.5em;
- height: 2.5em;
- object-fit: cover;
- border-radius: 2px;
-}
-
-li.project-item span.project-icon {
- font-size: 2em;
- display: block;
- width: 45px;
- height: 45px;
- text-align: center;
- /* background: #0004; */
- /* border: 1px solid var(--on-background); */
- border-radius: 2px;
-}
-
-li.project-item a {
- text-decoration: none;
-}
-
-li.project-item p {
- margin: 0;
-}
-
div#web-buttons {
margin: 2rem 0;
}
@@ -223,112 +148,3 @@ div#web-buttons {
box-shadow: 1px 1px 0 #eee, 2px 2px 0 #eee;
}
-#live-banner {
- margin: 1em 0 2em 0;
- padding: 1em;
- border-radius: 4px;
- border: 1px solid var(--primary);
- box-shadow: 0 0 8px var(--primary);
-}
-
-#live-banner p {
- margin: 0;
-}
-
-.live-highlight {
- color: var(--primary);
-}
-
-.live-preview {
- display: flex;
- flex-direction: row;
- justify-content: start;
- gap: 1em;
-}
-
-.live-preview div:first-of-type {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- gap: .3em;
-}
-
-.live-thumbnail {
- border-radius: 4px;
-}
-
-.live-button {
- margin: .2em;
- padding: .4em .5em;
- display: inline-block;
- color: var(--primary);
- border: 1px solid var(--primary);
- border-radius: 4px;
- transition: color .1s linear, background-color .1s linear, box-shadow .1s linear;
-}
-
-.live-button:hover {
- color: var(--background);
- background-color: var(--primary);
- box-shadow: 0 0 8px var(--primary);
- text-decoration: none;
-}
-
-.live-info {
- display: flex;
- flex-direction: column;
- gap: .3em;
- overflow-x: hidden;
-}
-
-#live-banner h2 {
- margin: 0;
- color: var(--on-background);
- font-family: 'Inter', sans-serif;
- font-weight: 800;
- font-style: italic;
-}
-
-.live-pinger {
- width: .5em;
- height: .5em;
- margin: .1em .2em;
- display: inline-block;
- border-radius: 100%;
- background-color: var(--primary);
- box-shadow: 0 0 4px var(--primary);
- animation: live-pinger-pulse 1s infinite alternate ease-in-out;
-}
-
-@keyframes live-pinger-pulse {
- from {
- opacity: .8;
- transform: scale(1.0);
- }
- to {
- opacity: 1;
- transform: scale(1.1);
- }
-}
-
-.live-game {
- overflow: hidden;
- text-wrap: nowrap;
- text-overflow: ellipsis;
-}
-
-.live-game .live-game-prefix {
- opacity: .8;
-}
-
-.live-title {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
-}
-
-.live-viewers {
- opacity: .5;
-}
diff --git a/public/style/main.css b/public/style/main.css
index 680ee2b..4e9e113 100644
--- a/public/style/main.css
+++ b/public/style/main.css
@@ -3,7 +3,6 @@
@import url("/style/footer.css");
@import url("/style/prideflag.css");
@import url("/style/cursor.css");
-@import url("/font/inter/inter.css");
@font-face {
font-family: "Monaspace Argon";
diff --git a/schema-migration/000-init.sql b/schema-migration/000-init.sql
index 90385ac..f70dee6 100644
--- a/schema-migration/000-init.sql
+++ b/schema-migration/000-init.sql
@@ -18,9 +18,7 @@ CREATE TABLE arimelody.account (
password TEXT NOT NULL,
email TEXT,
avatar_url TEXT,
- created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
- fail_attempts INT NOT NULL DEFAULT 0,
- locked BOOLEAN DEFAULT false
+ created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
);
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
@@ -86,7 +84,7 @@ CREATE TABLE arimelody.musicrelease (
buylink text,
copyright text,
copyrightURL text,
- created_at TIMESTAMP NOT NULL DEFAULT current_timestamp
+ created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
);
ALTER TABLE arimelody.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id);
diff --git a/schema-migration/003-fail-lock.sql b/schema-migration/003-fail-lock.sql
deleted file mode 100644
index 32d19bf..0000000
--- a/schema-migration/003-fail-lock.sql
+++ /dev/null
@@ -1,3 +0,0 @@
--- it would be nice to prevent brute-forcing
-ALTER TABLE arimelody.account ADD COLUMN fail_attempts INT NOT NULL DEFAULT 0;
-ALTER TABLE arimelody.account ADD COLUMN locked BOOLEAN DEFAULT false;
diff --git a/templates/templates.go b/templates/templates.go
index 752c78d..8d1a5ca 100644
--- a/templates/templates.go
+++ b/templates/templates.go
@@ -1,8 +1,8 @@
package templates
import (
- "html/template"
- "path/filepath"
+ "html/template"
+ "path/filepath"
)
var IndexTemplate = template.Must(template.ParseFiles(
diff --git a/view/index.go b/view/index.go
deleted file mode 100644
index b6e3891..0000000
--- a/view/index.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package view
-
-import (
- "arimelody-web/controller"
- "arimelody-web/model"
- "arimelody-web/templates"
- "fmt"
- "net/http"
- "os"
-)
-
-func IndexHandler(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.Method == http.MethodHead {
- w.WriteHeader(http.StatusOK)
- return
- }
-
- type IndexData struct {
- TwitchStatus *model.TwitchStreamInfo
- }
-
- var err error
- var twitchStatus *model.TwitchStreamInfo = nil
- if app.Twitch != nil && len(app.Config.Twitch.Broadcaster) > 0 {
- twitchStatus, err = controller.GetTwitchStatus(app, app.Config.Twitch.Broadcaster)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to get Twitch status for %s: %v\n", app.Config.Twitch.Broadcaster, err)
- }
- }
-
- if r.URL.Path == "/" || r.URL.Path == "/index.html" {
- err := templates.IndexTemplate.Execute(w, IndexData{
- TwitchStatus: twitchStatus,
- })
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to render index page: %v\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- return
- }
-
- StaticHandler("public").ServeHTTP(w, r)
- })
-}
diff --git a/view/music.go b/view/music.go
index 8ed5279..2d40ef0 100644
--- a/view/music.go
+++ b/view/music.go
@@ -1,13 +1,13 @@
package view
import (
- "fmt"
- "net/http"
- "os"
+ "fmt"
+ "net/http"
+ "os"
- "arimelody-web/controller"
- "arimelody-web/model"
- "arimelody-web/templates"
+ "arimelody-web/controller"
+ "arimelody-web/model"
+ "arimelody-web/templates"
)
// HTTP HANDLER METHODS
@@ -60,7 +60,7 @@ func ServeGateway(app *model.AppState, release *model.Release) http.Handler {
// only allow authorised users to view hidden releases
privileged := false
if !release.Visible {
- session, err := controller.GetSessionFromRequest(app, r)
+ session, err := controller.GetSessionFromRequest(app.DB, r)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
diff --git a/view/static.go b/view/static.go
deleted file mode 100644
index 52263a2..0000000
--- a/view/static.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package view
-
-import (
- "errors"
- "net/http"
- "os"
- "path/filepath"
-)
-
-func StaticHandler(directory string) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path)))
-
- // does the file exist?
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- http.NotFound(w, r)
- return
- }
- }
-
- // is thjs a directory? (forbidden)
- if info.IsDir() {
- http.NotFound(w, r)
- return
- }
-
- http.FileServer(http.Dir(directory)).ServeHTTP(w, r)
- })
-}
-
diff --git a/views/footer.html b/views/footer.html
index eccf125..217c4b5 100644
--- a/views/footer.html
+++ b/views/footer.html
@@ -2,12 +2,7 @@
diff --git a/views/header.html b/views/header.html
index 03a8384..291b8ac 100644
--- a/views/header.html
+++ b/views/header.html
@@ -23,6 +23,9 @@
# hello, world!
@@ -50,7 +33,7 @@
- i'm a musician, developer, + i'm a musician, developer, streamer, youtuber, and probably a bunch of other things i forgot to mention!
@@ -61,7 +44,7 @@if you're looking to support me financially, that's so cool of you!! if you like, you can buy some of my music over on - bandcamp + bandcamp so you can at least get something for your money. thank you very much either way!! 💕
@@ -101,97 +84,57 @@where to find me 🛰️
-projects i've worked on 🛠️
-minecraft server query utility
-watch the cat dance 🐱
-progressive pride flag widget for websites
-silly hackerman IP address generator
-impact meme generator
-communal online text buffer
-lightweight reactive state library
-