From b150fa491cf668174e6f41bc579b8e08a18cc786 Mon Sep 17 00:00:00 2001 From: ari melody Date: Wed, 10 Sep 2025 13:50:46 +0100 Subject: [PATCH 01/27] update web buttons --- public/img/buttons/wangleline.png | Bin 0 -> 6479 bytes views/index.html | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 public/img/buttons/wangleline.png diff --git a/public/img/buttons/wangleline.png b/public/img/buttons/wangleline.png new file mode 100644 index 0000000000000000000000000000000000000000..b26ea3f59c6185ff6d8a1008316b4b03706c71db GIT binary patch literal 6479 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 thermia web button + + WangleLine button + elke web button From e5dcc4b8847b08f0fb6efb8587660a16da5955cd Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 19:03:35 +0100 Subject: [PATCH 02/27] embed html template and static files --- Makefile | 4 +- admin/accounthttp.go | 29 ++-- admin/artisthttp.go | 13 +- admin/http.go | 57 ++++---- admin/logshttp.go | 15 +- admin/releasehttp.go | 29 ++-- admin/templates.go | 125 ----------------- .../html}/components/credits/addcredit.html | 0 .../html}/components/credits/editcredits.html | 0 .../html}/components/credits/newcredit.html | 0 .../html}/components/links/editlinks.html | 0 .../components/release/release-list-item.html | 0 .../html}/components/tracks/addtrack.html | 0 .../html}/components/tracks/edittracks.html | 0 .../html}/components/tracks/newtrack.html | 0 .../html}/edit-account.html | 0 .../html}/edit-artist.html | 0 .../html}/edit-release.html | 0 .../{views => templates/html}/edit-track.html | 0 admin/{views => templates/html}/index.html | 0 admin/{views => templates/html}/layout.html | 0 .../{views => templates/html}/login-totp.html | 0 admin/{views => templates/html}/login.html | 0 admin/{views => templates/html}/logout.html | 0 admin/{views => templates/html}/logs.html | 0 .../templates/html}/prideflag.html | 0 admin/{views => templates/html}/register.html | 0 .../html}/totp-confirm.html | 0 .../{views => templates/html}/totp-setup.html | 0 admin/templates/templates.go | 128 ++++++++++++++++++ admin/trackhttp.go | 13 +- main.go | 55 ++++---- model/appstate.go | 7 +- {views => templates/html}/404.html | 0 {views => templates/html}/footer.html | 0 {views => templates/html}/header.html | 0 {views => templates/html}/index.html | 0 {views => templates/html}/layout.html | 0 {views => templates/html}/music-gateway.html | 0 {views => templates/html}/music.html | 0 templates/html/prideflag.html | 21 +++ templates/templates.go | 52 ++++--- view/{static.go => files.go} | 21 ++- view/index.go | 2 +- 44 files changed, 316 insertions(+), 255 deletions(-) delete mode 100644 admin/templates.go rename admin/{ => templates/html}/components/credits/addcredit.html (100%) rename admin/{ => templates/html}/components/credits/editcredits.html (100%) rename admin/{ => templates/html}/components/credits/newcredit.html (100%) rename admin/{ => templates/html}/components/links/editlinks.html (100%) rename admin/{ => templates/html}/components/release/release-list-item.html (100%) rename admin/{ => templates/html}/components/tracks/addtrack.html (100%) rename admin/{ => templates/html}/components/tracks/edittracks.html (100%) rename admin/{ => templates/html}/components/tracks/newtrack.html (100%) rename admin/{views => templates/html}/edit-account.html (100%) rename admin/{views => templates/html}/edit-artist.html (100%) rename admin/{views => templates/html}/edit-release.html (100%) rename admin/{views => templates/html}/edit-track.html (100%) rename admin/{views => templates/html}/index.html (100%) rename admin/{views => templates/html}/layout.html (100%) rename admin/{views => templates/html}/login-totp.html (100%) rename admin/{views => templates/html}/login.html (100%) rename admin/{views => templates/html}/logout.html (100%) rename admin/{views => templates/html}/logs.html (100%) rename {views => admin/templates/html}/prideflag.html (100%) rename admin/{views => templates/html}/register.html (100%) rename admin/{views => templates/html}/totp-confirm.html (100%) rename admin/{views => templates/html}/totp-setup.html (100%) create mode 100644 admin/templates/templates.go rename {views => templates/html}/404.html (100%) rename {views => templates/html}/footer.html (100%) rename {views => templates/html}/header.html (100%) rename {views => templates/html}/index.html (100%) rename {views => templates/html}/layout.html (100%) rename {views => templates/html}/music-gateway.html (100%) rename {views => templates/html}/music.html (100%) create mode 100644 templates/html/prideflag.html rename view/{static.go => files.go} (54%) diff --git a/Makefile b/Makefile index 11e565a..f96321c 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ EXEC = arimelody-web .PHONY: $(EXEC) -$(EXEC): +build: GOOS=linux GOARCH=amd64 go build -o $(EXEC) -bundle: $(EXEC) +bundle: build tar czf $(EXEC).tar.gz $(EXEC) admin/components/ admin/views/ admin/static/ views/ public/ schema-migration/ clean: diff --git a/admin/accounthttp.go b/admin/accounthttp.go index 945a507..b2c3b0d 100644 --- a/admin/accounthttp.go +++ b/admin/accounthttp.go @@ -1,17 +1,18 @@ package admin import ( - "database/sql" - "fmt" - "net/http" - "net/url" - "os" + "database/sql" + "fmt" + "net/http" + "net/url" + "os" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/admin/templates" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" - "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/bcrypt" ) func accountHandler(app *model.AppState) http.Handler { @@ -64,7 +65,7 @@ func accountIndexHandler(app *model.AppState) http.Handler { session.Message = sessionMessage session.Error = sessionError - err = accountTemplate.Execute(w, accountResponse{ + err = templates.AccountTemplate.Execute(w, accountResponse{ Session: session, TOTPs: totps, }) @@ -184,7 +185,7 @@ func totpSetupHandler(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session }) + err := templates.TOTPSetupTemplate.Execute(w, totpSetupData{ Session: session }) if err != nil { fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -221,7 +222,7 @@ func totpSetupHandler(app *model.AppState) http.Handler { if err != nil { fmt.Printf("WARN: Failed to create TOTP method: %s\n", err) controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") - err := totpSetupTemplate.Execute(w, totpConfirmData{ Session: session }) + err := templates.TOTPSetupTemplate.Execute(w, totpConfirmData{ Session: session }) if err != nil { fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -235,7 +236,7 @@ func totpSetupHandler(app *model.AppState) http.Handler { fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) } - err = totpConfirmTemplate.Execute(w, totpConfirmData{ + err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ Session: session, TOTP: &totp, NameEscaped: url.PathEscape(totp.Name), @@ -296,7 +297,7 @@ func totpConfirmHandler(app *model.AppState) http.Handler { confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1) if code != confirmCodeOffset { session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." } - err = totpConfirmTemplate.Execute(w, totpConfirmData{ + err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ Session: session, TOTP: totp, NameEscaped: url.PathEscape(totp.Name), diff --git a/admin/artisthttp.go b/admin/artisthttp.go index 9fa6bb2..67ea7d2 100644 --- a/admin/artisthttp.go +++ b/admin/artisthttp.go @@ -1,12 +1,13 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/model" - "arimelody-web/controller" + "arimelody-web/admin/templates" + "arimelody-web/controller" + "arimelody-web/model" ) func serveArtist(app *model.AppState) http.Handler { @@ -39,7 +40,7 @@ func serveArtist(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err = artistTemplate.Execute(w, ArtistResponse{ + err = templates.EditArtistTemplate.Execute(w, ArtistResponse{ Session: session, Artist: artist, Credits: credits, diff --git a/admin/http.go b/admin/http.go index 245a152..05e181d 100644 --- a/admin/http.go +++ b/admin/http.go @@ -1,20 +1,24 @@ package admin import ( - "context" - "database/sql" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - "time" + "context" + "database/sql" + "embed" + "fmt" + "mime" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/admin/templates" + "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 { @@ -91,7 +95,7 @@ func AdminIndexHandler(app *model.AppState) http.Handler { Tracks []*model.Track } - err = indexTemplate.Execute(w, IndexData{ + err = templates.IndexTemplate.Execute(w, IndexData{ Session: session, Releases: releases, Artists: artists, @@ -120,7 +124,7 @@ func registerAccountHandler(app *model.AppState) http.Handler { } render := func() { - err := registerTemplate.Execute(w, registerData{ Session: session }) + err := templates.RegisterTemplate.Execute(w, registerData{ Session: session }) if err != nil { fmt.Printf("WARN: Error rendering create account page: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -230,7 +234,7 @@ func loginHandler(app *model.AppState) http.Handler { } render := func() { - err := loginTemplate.Execute(w, loginData{ Session: session }) + err := templates.LoginTemplate.Execute(w, loginData{ Session: session }) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -346,7 +350,7 @@ func loginTOTPHandler(app *model.AppState) http.Handler { } render := func() { - err := loginTOTPTemplate.Execute(w, loginTOTPData{ Session: session }) + err := templates.LoginTOTPTemplate.Execute(w, loginTOTPData{ Session: session }) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to render login TOTP page: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -440,7 +444,7 @@ func logoutHandler(app *model.AppState) http.Handler { Path: "/", }) - err = logoutTemplate.Execute(w, nil) + err = templates.LogoutTemplate.Execute(w, nil) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to render logout page: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -460,24 +464,21 @@ func requireAccount(next http.Handler) http.HandlerFunc { }) } +//go:embed "static" +var staticFS embed.FS + func staticHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path))) - // does the file exist? + file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(r.URL.Path))) if err != nil { - if os.IsNotExist(err) { - http.NotFound(w, r) - return - } - } - - // is thjs a directory? (forbidden) - if info.IsDir() { http.NotFound(w, r) return } - http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r) + w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path))) + w.WriteHeader(http.StatusOK) + + w.Write(file) }) } diff --git a/admin/logshttp.go b/admin/logshttp.go index 7249b16..99f0ef1 100644 --- a/admin/logshttp.go +++ b/admin/logshttp.go @@ -1,12 +1,13 @@ package admin import ( - "arimelody-web/log" - "arimelody-web/model" - "fmt" - "net/http" - "os" - "strings" + "arimelody-web/admin/templates" + "arimelody-web/log" + "arimelody-web/model" + "fmt" + "net/http" + "os" + "strings" ) func logsHandler(app *model.AppState) http.Handler { @@ -54,7 +55,7 @@ func logsHandler(app *model.AppState) http.Handler { Logs []*log.Log } - err = logsTemplate.Execute(w, LogsResponse{ + err = templates.LogsTemplate.Execute(w, LogsResponse{ Session: session, Logs: logs, }) diff --git a/admin/releasehttp.go b/admin/releasehttp.go index c6b68ab..30c967b 100644 --- a/admin/releasehttp.go +++ b/admin/releasehttp.go @@ -1,12 +1,13 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/admin/templates" + "arimelody-web/controller" + "arimelody-web/model" ) func serveRelease(app *model.AppState) http.Handler { @@ -60,7 +61,7 @@ func serveRelease(app *model.AppState) http.Handler { Release *model.Release } - err = releaseTemplate.Execute(w, ReleaseResponse{ + err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{ Session: session, Release: release, }) @@ -74,7 +75,7 @@ func serveRelease(app *model.AppState) http.Handler { func serveEditCredits(release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") - err := editCreditsTemplate.Execute(w, release) + err := templates.EditCreditsTemplate.Execute(w, release) if err != nil { fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -97,7 +98,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = addCreditTemplate.Execute(w, response{ + err = templates.AddCreditTemplate.Execute(w, response{ ReleaseID: release.ID, Artists: artists, }) @@ -123,7 +124,7 @@ func serveNewCredit(app *model.AppState) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = newCreditTemplate.Execute(w, artist) + err = templates.NewCreditTemplate.Execute(w, artist) if err != nil { fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -134,7 +135,7 @@ func serveNewCredit(app *model.AppState) http.Handler { func serveEditLinks(release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") - err := editLinksTemplate.Execute(w, release) + err := templates.EditCreditsTemplate.Execute(w, release) if err != nil { fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -151,7 +152,7 @@ func serveEditTracks(release *model.Release) http.Handler { Add func(a int, b int) int } - err := editTracksTemplate.Execute(w, editTracksData{ + err := templates.EditTracksTemplate.Execute(w, editTracksData{ Release: release, Add: func(a, b int) int { return a + b }, }) @@ -177,7 +178,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = addTrackTemplate.Execute(w, response{ + err = templates.AddTrackTemplate.Execute(w, response{ ReleaseID: release.ID, Tracks: tracks, }) @@ -185,7 +186,6 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler { fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } - return }) } @@ -204,11 +204,10 @@ func serveNewTrack(app *model.AppState) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = newTrackTemplate.Execute(w, track) + err = templates.NewTrackTemplate.Execute(w, track) if err != nil { fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } - return }) } diff --git a/admin/templates.go b/admin/templates.go deleted file mode 100644 index 606d569..0000000 --- a/admin/templates.go +++ /dev/null @@ -1,125 +0,0 @@ -package admin - -import ( - "arimelody-web/log" - "fmt" - "html/template" - "path/filepath" - "strings" - "time" -) - -var indexTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "components", "release", "release-list-item.html"), - filepath.Join("admin", "views", "index.html"), -)) - -var loginTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "login.html"), -)) -var loginTOTPTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "login-totp.html"), -)) -var registerTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "register.html"), -)) -var logoutTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "logout.html"), -)) -var accountTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "edit-account.html"), -)) -var totpSetupTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "totp-setup.html"), -)) -var totpConfirmTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "totp-confirm.html"), -)) - -var logsTemplate = template.Must(template.New("layout.html").Funcs(template.FuncMap{ - "parseLevel": func(level log.LogLevel) string { - switch level { - case log.LEVEL_INFO: - return "INFO" - case log.LEVEL_WARN: - return "WARN" - } - return fmt.Sprintf("%d?", level) - }, - "titleCase": func(logType string) string { - runes := []rune(logType) - for i, r := range runes { - if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { - runes[i] = r + ('A' - 'a') - } - } - return string(runes) - }, - "lower": func(str string) string { return strings.ToLower(str) }, - "prettyTime": func(t time.Time) string { - // return t.Format("2006-01-02 15:04:05") - // return t.Format("15:04:05, 2 Jan 2006") - return t.Format("02 Jan 2006, 15:04:05") - }, -}).ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "logs.html"), -)) - -var releaseTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "edit-release.html"), -)) -var artistTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "views", "edit-artist.html"), -)) -var trackTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "views", "layout.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("admin", "components", "release", "release-list-item.html"), - filepath.Join("admin", "views", "edit-track.html"), -)) - -var editCreditsTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "credits", "editcredits.html"), -)) -var addCreditTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "credits", "addcredit.html"), -)) -var newCreditTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "credits", "newcredit.html"), -)) - -var editLinksTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "links", "editlinks.html"), -)) - -var editTracksTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "tracks", "edittracks.html"), -)) -var addTrackTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "tracks", "addtrack.html"), -)) -var newTrackTemplate = template.Must(template.ParseFiles( - filepath.Join("admin", "components", "tracks", "newtrack.html"), -)) diff --git a/admin/components/credits/addcredit.html b/admin/templates/html/components/credits/addcredit.html similarity index 100% rename from admin/components/credits/addcredit.html rename to admin/templates/html/components/credits/addcredit.html diff --git a/admin/components/credits/editcredits.html b/admin/templates/html/components/credits/editcredits.html similarity index 100% rename from admin/components/credits/editcredits.html rename to admin/templates/html/components/credits/editcredits.html diff --git a/admin/components/credits/newcredit.html b/admin/templates/html/components/credits/newcredit.html similarity index 100% rename from admin/components/credits/newcredit.html rename to admin/templates/html/components/credits/newcredit.html diff --git a/admin/components/links/editlinks.html b/admin/templates/html/components/links/editlinks.html similarity index 100% rename from admin/components/links/editlinks.html rename to admin/templates/html/components/links/editlinks.html diff --git a/admin/components/release/release-list-item.html b/admin/templates/html/components/release/release-list-item.html similarity index 100% rename from admin/components/release/release-list-item.html rename to admin/templates/html/components/release/release-list-item.html diff --git a/admin/components/tracks/addtrack.html b/admin/templates/html/components/tracks/addtrack.html similarity index 100% rename from admin/components/tracks/addtrack.html rename to admin/templates/html/components/tracks/addtrack.html diff --git a/admin/components/tracks/edittracks.html b/admin/templates/html/components/tracks/edittracks.html similarity index 100% rename from admin/components/tracks/edittracks.html rename to admin/templates/html/components/tracks/edittracks.html diff --git a/admin/components/tracks/newtrack.html b/admin/templates/html/components/tracks/newtrack.html similarity index 100% rename from admin/components/tracks/newtrack.html rename to admin/templates/html/components/tracks/newtrack.html diff --git a/admin/views/edit-account.html b/admin/templates/html/edit-account.html similarity index 100% rename from admin/views/edit-account.html rename to admin/templates/html/edit-account.html diff --git a/admin/views/edit-artist.html b/admin/templates/html/edit-artist.html similarity index 100% rename from admin/views/edit-artist.html rename to admin/templates/html/edit-artist.html diff --git a/admin/views/edit-release.html b/admin/templates/html/edit-release.html similarity index 100% rename from admin/views/edit-release.html rename to admin/templates/html/edit-release.html diff --git a/admin/views/edit-track.html b/admin/templates/html/edit-track.html similarity index 100% rename from admin/views/edit-track.html rename to admin/templates/html/edit-track.html diff --git a/admin/views/index.html b/admin/templates/html/index.html similarity index 100% rename from admin/views/index.html rename to admin/templates/html/index.html diff --git a/admin/views/layout.html b/admin/templates/html/layout.html similarity index 100% rename from admin/views/layout.html rename to admin/templates/html/layout.html diff --git a/admin/views/login-totp.html b/admin/templates/html/login-totp.html similarity index 100% rename from admin/views/login-totp.html rename to admin/templates/html/login-totp.html diff --git a/admin/views/login.html b/admin/templates/html/login.html similarity index 100% rename from admin/views/login.html rename to admin/templates/html/login.html diff --git a/admin/views/logout.html b/admin/templates/html/logout.html similarity index 100% rename from admin/views/logout.html rename to admin/templates/html/logout.html diff --git a/admin/views/logs.html b/admin/templates/html/logs.html similarity index 100% rename from admin/views/logs.html rename to admin/templates/html/logs.html diff --git a/views/prideflag.html b/admin/templates/html/prideflag.html similarity index 100% rename from views/prideflag.html rename to admin/templates/html/prideflag.html diff --git a/admin/views/register.html b/admin/templates/html/register.html similarity index 100% rename from admin/views/register.html rename to admin/templates/html/register.html diff --git a/admin/views/totp-confirm.html b/admin/templates/html/totp-confirm.html similarity index 100% rename from admin/views/totp-confirm.html rename to admin/templates/html/totp-confirm.html diff --git a/admin/views/totp-setup.html b/admin/templates/html/totp-setup.html similarity index 100% rename from admin/views/totp-setup.html rename to admin/templates/html/totp-setup.html diff --git a/admin/templates/templates.go b/admin/templates/templates.go new file mode 100644 index 0000000..58cd1d0 --- /dev/null +++ b/admin/templates/templates.go @@ -0,0 +1,128 @@ +package templates + +import ( + "arimelody-web/log" + "fmt" + "html/template" + "strings" + "time" + _ "embed" +) + +//go:embed "html/layout.html" +var layoutHTML string +//go:embed "html/prideflag.html" +var prideflagHTML string + +//go:embed "html/index.html" +var indexHTML string + +//go:embed "html/register.html" +var registerHTML string +//go:embed "html/login.html" +var loginHTML string +//go:embed "html/login-totp.html" +var loginTotpHTML string +//go:embed "html/totp-confirm.html" +var totpConfirmHTML string +//go:embed "html/totp-setup.html" +var totpSetupHTML string +//go:embed "html/logout.html" +var logoutHTML string + +//go:embed "html/logs.html" +var logsHTML string + +//go:embed "html/edit-account.html" +var editAccountHTML string +//go:embed "html/edit-artist.html" +var editArtistHTML string +//go:embed "html/edit-release.html" +var editReleaseHTML string +//go:embed "html/edit-track.html" +var editTrackHTML string + +//go:embed "html/components/credits/newcredit.html" +var componentNewCreditHTML string +//go:embed "html/components/credits/addcredit.html" +var componentAddCreditHTML string +//go:embed "html/components/credits/editcredits.html" +var componentEditCreditsHTML string + +//go:embed "html/components/links/editlinks.html" +var componentEditLinksHTML string + +//go:embed "html/components/release/release-list-item.html" +var componentReleaseListItemHTML string + +//go:embed "html/components/tracks/newtrack.html" +var componentNewTrackHTML string +//go:embed "html/components/tracks/addtrack.html" +var componentAddTrackHTML string +//go:embed "html/components/tracks/edittracks.html" +var componentEditTracksHTML string + +var BaseTemplate = template.Must(template.New("base").Parse( + strings.Join([]string{ layoutHTML, prideflagHTML }, "\n"), +)) + +var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( + strings.Join([]string{ + indexHTML, + componentReleaseListItemHTML, + }, "\n"), +)) + +var LoginTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginHTML)) +var LoginTOTPTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginTotpHTML)) +var RegisterTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(registerHTML)) +var LogoutTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(logoutHTML)) +var AccountTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editAccountHTML)) +var TOTPSetupTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpSetupHTML)) +var TOTPConfirmTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpConfirmHTML)) + +var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(template.FuncMap{ + "parseLevel": func(level log.LogLevel) string { + switch level { + case log.LEVEL_INFO: + return "INFO" + case log.LEVEL_WARN: + return "WARN" + } + return fmt.Sprintf("%d?", level) + }, + "titleCase": func(logType string) string { + runes := []rune(logType) + for i, r := range runes { + if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { + runes[i] = r + ('A' - 'a') + } + } + return string(runes) + }, + "lower": func(str string) string { return strings.ToLower(str) }, + "prettyTime": func(t time.Time) string { + // return t.Format("2006-01-02 15:04:05") + // return t.Format("15:04:05, 2 Jan 2006") + return t.Format("02 Jan 2006, 15:04:05") + }, +}).Parse(logsHTML)) + +var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML)) +var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML)) +var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( + strings.Join([]string{ + editTrackHTML, + componentReleaseListItemHTML, + }, "\n"), +)) + +var EditCreditsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditCreditsHTML)) +var AddCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddCreditHTML)) +var NewCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewCreditHTML)) + +var EditLinksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditLinksHTML)) + +var EditTracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditTracksHTML)) +var AddTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddTrackHTML)) +var NewTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewTrackHTML)) diff --git a/admin/trackhttp.go b/admin/trackhttp.go index 93eacdb..e93d1bb 100644 --- a/admin/trackhttp.go +++ b/admin/trackhttp.go @@ -1,12 +1,13 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/model" - "arimelody-web/controller" + "arimelody-web/admin/templates" + "arimelody-web/controller" + "arimelody-web/model" ) func serveTrack(app *model.AppState) http.Handler { @@ -39,7 +40,7 @@ func serveTrack(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err = trackTemplate.Execute(w, TrackResponse{ + err = templates.EditTrackTemplate.Execute(w, TrackResponse{ Session: session, Track: track, Releases: releases, diff --git a/main.go b/main.go index eaed7c6..553f109 100644 --- a/main.go +++ b/main.go @@ -1,32 +1,33 @@ package main import ( - "bufio" - "errors" - "fmt" - stdLog "log" - "math" - "math/rand" - "net" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "time" + "bufio" + "embed" + "errors" + "fmt" + stdLog "log" + "math" + "math/rand" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" - "arimelody-web/admin" - "arimelody-web/api" - "arimelody-web/colour" - "arimelody-web/controller" - "arimelody-web/cursor" - "arimelody-web/log" - "arimelody-web/model" - "arimelody-web/view" + "arimelody-web/admin" + "arimelody-web/api" + "arimelody-web/colour" + "arimelody-web/controller" + "arimelody-web/cursor" + "arimelody-web/log" + "arimelody-web/model" + "arimelody-web/view" - "github.com/jmoiron/sqlx" - _ "github.com/lib/pq" - "golang.org/x/crypto/bcrypt" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "golang.org/x/crypto/bcrypt" ) // used for database migrations @@ -35,12 +36,16 @@ const DB_VERSION = 1 const DEFAULT_PORT int64 = 8080 const HRT_DATE int64 = 1756478697 +//go:embed "public" +var publicFS embed.FS + func main() { fmt.Printf("made with <3 by ari melody\n\n") app := model.AppState{ Config: controller.GetConfig(), Twitch: nil, + PublicFS: publicFS, } // initialise database connection @@ -526,7 +531,7 @@ func createServeMux(app *model.AppState) *http.ServeMux { mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app))) mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app))) mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app))) - mux.Handle("/uploads/", http.StripPrefix("/uploads", view.StaticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) + mux.Handle("/uploads/", http.StripPrefix("/uploads", view.ServeFiles(filepath.Join(app.Config.DataDirectory, "uploads")))) mux.Handle("/cursor-ws", cursor.Handler(app)) mux.Handle("/", view.IndexHandler(app)) diff --git a/model/appstate.go b/model/appstate.go index 3a1c230..1a13be9 100644 --- a/model/appstate.go +++ b/model/appstate.go @@ -1,9 +1,11 @@ package model import ( - "github.com/jmoiron/sqlx" + "embed" - "arimelody-web/log" + "github.com/jmoiron/sqlx" + + "arimelody-web/log" ) type ( @@ -43,5 +45,6 @@ type ( Config Config Log log.Logger Twitch *TwitchState + PublicFS embed.FS } ) diff --git a/views/404.html b/templates/html/404.html similarity index 100% rename from views/404.html rename to templates/html/404.html diff --git a/views/footer.html b/templates/html/footer.html similarity index 100% rename from views/footer.html rename to templates/html/footer.html diff --git a/views/header.html b/templates/html/header.html similarity index 100% rename from views/header.html rename to templates/html/header.html diff --git a/views/index.html b/templates/html/index.html similarity index 100% rename from views/index.html rename to templates/html/index.html diff --git a/views/layout.html b/templates/html/layout.html similarity index 100% rename from views/layout.html rename to templates/html/layout.html diff --git a/views/music-gateway.html b/templates/html/music-gateway.html similarity index 100% rename from views/music-gateway.html rename to templates/html/music-gateway.html diff --git a/views/music.html b/templates/html/music.html similarity index 100% rename from views/music.html rename to templates/html/music.html diff --git a/templates/html/prideflag.html b/templates/html/prideflag.html new file mode 100644 index 0000000..47ce4c7 --- /dev/null +++ b/templates/html/prideflag.html @@ -0,0 +1,21 @@ +{{define "prideflag"}} + + + + + + + + + + + + + + + + + + + +{{end}} diff --git a/templates/templates.go b/templates/templates.go index 752c78d..c6ab41e 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -1,28 +1,36 @@ package templates import ( - "html/template" - "path/filepath" + _ "embed" + "html/template" + "strings" ) -var IndexTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "index.html"), -)) -var MusicTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "music.html"), -)) -var MusicGatewayTemplate = template.Must(template.ParseFiles( - filepath.Join("views", "layout.html"), - filepath.Join("views", "header.html"), - filepath.Join("views", "footer.html"), - filepath.Join("views", "prideflag.html"), - filepath.Join("views", "music-gateway.html"), +//go:embed "html/layout.html" +var layoutHTML string +//go:embed "html/header.html" +var headerHTML string +//go:embed "html/footer.html" +var footerHTML string +//go:embed "html/prideflag.html" +var prideflagHTML string +//go:embed "html/index.html" +var indexHTML string +//go:embed "html/music.html" +var musicHTML string +//go:embed "html/music-gateway.html" +var musicGatewayHTML string +// //go:embed "html/404.html" +// var error404HTML string + +var BaseTemplate = template.Must(template.New("base").Parse( + strings.Join([]string{ + layoutHTML, + headerHTML, + footerHTML, + prideflagHTML, + }, "\n"), )) +var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(indexHTML)) +var MusicTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicHTML)) +var MusicGatewayTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicGatewayHTML)) diff --git a/view/static.go b/view/files.go similarity index 54% rename from view/static.go rename to view/files.go index 52263a2..c1b0e29 100644 --- a/view/static.go +++ b/view/files.go @@ -1,13 +1,31 @@ package view import ( + "embed" "errors" + "mime" "net/http" "os" + "path" "path/filepath" ) -func StaticHandler(directory string) http.Handler { +func ServeEmbedFS(fs embed.FS, dir string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + file, err := fs.ReadFile(filepath.Join(dir, filepath.Clean(r.URL.Path))) + if err != nil { + http.NotFound(w, r) + return + } + + w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path))) + w.WriteHeader(http.StatusOK) + + w.Write(file) + }) +} + +func ServeFiles(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))) @@ -28,4 +46,3 @@ func StaticHandler(directory string) http.Handler { http.FileServer(http.Dir(directory)).ServeHTTP(w, r) }) } - diff --git a/view/index.go b/view/index.go index b6e3891..bcd0d06 100644 --- a/view/index.go +++ b/view/index.go @@ -40,6 +40,6 @@ func IndexHandler(app *model.AppState) http.Handler { return } - StaticHandler("public").ServeHTTP(w, r) + ServeEmbedFS(app.PublicFS, "public").ServeHTTP(w, r) }) } From 1999ab7d2c493f9440f5b50950e665059d33f6a2 Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 22:29:37 +0100 Subject: [PATCH 03/27] embed schema migration scripts --- controller/migrator.go | 20 +++++++++++++------ .../schema-migration}/000-init.sql | 0 .../schema-migration}/001-pre-versioning.sql | 0 .../schema-migration}/002-audit-logs.sql | 0 .../schema-migration}/003-fail-lock.sql | 0 controller/schema-migration/004-test.sql | 1 + 6 files changed, 15 insertions(+), 6 deletions(-) rename {schema-migration => controller/schema-migration}/000-init.sql (100%) rename {schema-migration => controller/schema-migration}/001-pre-versioning.sql (100%) rename {schema-migration => controller/schema-migration}/002-audit-logs.sql (100%) rename {schema-migration => controller/schema-migration}/003-fail-lock.sql (100%) create mode 100644 controller/schema-migration/004-test.sql diff --git a/controller/migrator.go b/controller/migrator.go index 4b99b9c..1a70e62 100644 --- a/controller/migrator.go +++ b/controller/migrator.go @@ -1,14 +1,15 @@ package controller import ( - "fmt" - "os" - "time" + "embed" + "fmt" + "os" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) -const DB_VERSION int = 4 +const DB_VERSION int = 5 func CheckDBVersionAndMigrate(db *sqlx.DB) { db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody") @@ -49,16 +50,23 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) { ApplyMigration(db, "003-fail-lock") oldDBVersion = 4 + case 4: + ApplyMigration(db, "004-test") + oldDBVersion = 5 + } } fmt.Printf("Database schema up to date.\n") } +//go:embed "schema-migration" +var schemaFS embed.FS + func ApplyMigration(db *sqlx.DB, scriptFile string) { fmt.Printf("Applying schema migration %s...\n", scriptFile) - bytes, err := os.ReadFile("schema-migration/" + scriptFile + ".sql") + bytes, err := schemaFS.ReadFile("schema-migration/" + scriptFile + ".sql") if err != nil { fmt.Fprintf(os.Stderr, "FATAL: Failed to open schema file \"%s\": %v\n", scriptFile, err) os.Exit(1) diff --git a/schema-migration/000-init.sql b/controller/schema-migration/000-init.sql similarity index 100% rename from schema-migration/000-init.sql rename to controller/schema-migration/000-init.sql diff --git a/schema-migration/001-pre-versioning.sql b/controller/schema-migration/001-pre-versioning.sql similarity index 100% rename from schema-migration/001-pre-versioning.sql rename to controller/schema-migration/001-pre-versioning.sql diff --git a/schema-migration/002-audit-logs.sql b/controller/schema-migration/002-audit-logs.sql similarity index 100% rename from schema-migration/002-audit-logs.sql rename to controller/schema-migration/002-audit-logs.sql diff --git a/schema-migration/003-fail-lock.sql b/controller/schema-migration/003-fail-lock.sql similarity index 100% rename from schema-migration/003-fail-lock.sql rename to controller/schema-migration/003-fail-lock.sql diff --git a/controller/schema-migration/004-test.sql b/controller/schema-migration/004-test.sql new file mode 100644 index 0000000..3733de2 --- /dev/null +++ b/controller/schema-migration/004-test.sql @@ -0,0 +1 @@ +INSERT INTO arimelody.auditlog (level, type, content) VALUES (0, "test", "this is a db schema migration test!"); From 42c6540ac3adbed4ab889d60bedf5805fd22ab7d Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 22:30:06 +0100 Subject: [PATCH 04/27] fix upload info log line --- api/uploads.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/uploads.go b/api/uploads.go index 4678f22..3c3c58a 100644 --- a/api/uploads.go +++ b/api/uploads.go @@ -50,7 +50,7 @@ func HandleImageUpload(app *model.AppState, data *string, directory string, file return "", nil } - app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext) + app.Log.Info(log.TYPE_FILES, "\"%s\" created.", imagePath) return filename, nil } From ef655744bb36e2b93c315accdf7e8b15824b06ea Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 22:30:31 +0100 Subject: [PATCH 05/27] comment out deprecated QR code. uh. code --- controller/qr.go | 69 ++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/controller/qr.go b/controller/qr.go index dd08637..21ed3e6 100644 --- a/controller/qr.go +++ b/controller/qr.go @@ -2,8 +2,8 @@ package controller import ( "encoding/base64" - "image" - "image/color" + // "image" + // "image/color" "github.com/skip2/go-qrcode" ) @@ -18,36 +18,35 @@ func GenerateQRCode(data string) (string, error) { } // vvv DEPRECATED vvv - -const margin = 4 - -type QRCodeECCLevel int64 -const ( - LOW QRCodeECCLevel = iota - MEDIUM - QUARTILE - HIGH -) - -func drawLargeAlignmentSquare(x int, y int, img *image.Gray) { - for yi := range 7 { - for xi := range 7 { - if (xi == 0 || xi == 6) || (yi == 0 || yi == 6) { - img.Set(x + xi, y + yi, color.Black) - } else if (xi > 1 && xi < 5) && (yi > 1 && yi < 5) { - img.Set(x + xi, y + yi, color.Black) - } - } - } -} - -func drawSmallAlignmentSquare(x int, y int, img *image.Gray) { - for yi := range 5 { - for xi := range 5 { - if (xi == 0 || xi == 4) || (yi == 0 || yi == 4) { - img.Set(x + xi, y + yi, color.Black) - } - } - } - img.Set(x + 2, y + 2, color.Black) -} +// const margin = 4 +// +// type QRCodeECCLevel int64 +// const ( +// LOW QRCodeECCLevel = iota +// MEDIUM +// QUARTILE +// HIGH +// ) +// +// func drawLargeAlignmentSquare(x int, y int, img *image.Gray) { +// for yi := range 7 { +// for xi := range 7 { +// if (xi == 0 || xi == 6) || (yi == 0 || yi == 6) { +// img.Set(x + xi, y + yi, color.Black) +// } else if (xi > 1 && xi < 5) && (yi > 1 && yi < 5) { +// img.Set(x + xi, y + yi, color.Black) +// } +// } +// } +// } +// +// func drawSmallAlignmentSquare(x int, y int, img *image.Gray) { +// for yi := range 5 { +// for xi := range 5 { +// if (xi == 0 || xi == 4) || (yi == 0 || yi == 4) { +// img.Set(x + xi, y + yi, color.Black) +// } +// } +// } +// img.Set(x + 2, y + 2, color.Black) +// } From 419781988aede87258eb06cb702db88c7c591dec Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 22:30:52 +0100 Subject: [PATCH 06/27] refactor errors.New to fmt.Errorf --- api/api.go | 5 ++--- controller/release.go | 13 ++++++------- controller/session.go | 5 ++--- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/api/api.go b/api/api.go index 398db4b..c5156c2 100644 --- a/api/api.go +++ b/api/api.go @@ -2,7 +2,6 @@ package api import ( "context" - "errors" "fmt" "net/http" "os" @@ -173,7 +172,7 @@ func getSession(app *model.AppState, r *http.Request) (*model.Session, error) { // check cookies first 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\n", err)) + return nil, fmt.Errorf("Failed to retrieve session cookie: %v\n", err) } if sessionCookie != nil { token = sessionCookie.Value @@ -188,7 +187,7 @@ func getSession(app *model.AppState, r *http.Request) (*model.Session, error) { session, err := controller.GetSession(app.DB, token) if err != nil && !strings.Contains(err.Error(), "no rows") { - return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v\n", err)) + return nil, fmt.Errorf("Failed to retrieve session: %v\n", err) } if session != nil { diff --git a/controller/release.go b/controller/release.go index 3dcad26..afd09fb 100644 --- a/controller/release.go +++ b/controller/release.go @@ -1,7 +1,6 @@ package controller import ( - "errors" "fmt" "arimelody-web/model" @@ -21,7 +20,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) { // get credits credits, err := GetReleaseCredits(db, id) if err != nil { - return nil, errors.New(fmt.Sprintf("Credits: %s", err)) + return nil, fmt.Errorf("Credits: %s", err) } for _, credit := range credits { release.Credits = append(release.Credits, credit) @@ -30,7 +29,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) { // get tracks tracks, err := GetReleaseTracks(db, id) if err != nil { - return nil, errors.New(fmt.Sprintf("Tracks: %s", err)) + return nil, fmt.Errorf("Tracks: %s", err) } for _, track := range tracks { release.Tracks = append(release.Tracks, track) @@ -39,7 +38,7 @@ func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) { // get links links, err := GetReleaseLinks(db, id) if err != nil { - return nil, errors.New(fmt.Sprintf("Links: %s", err)) + return nil, fmt.Errorf("Links: %s", err) } for _, link := range links { release.Links = append(release.Links, link) @@ -71,7 +70,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod // get credits credits, err := GetReleaseCredits(db, release.ID) if err != nil { - return nil, errors.New(fmt.Sprintf("Credits: %s", err)) + return nil, fmt.Errorf("Credits: %s", err) } for _, credit := range credits { release.Credits = append(release.Credits, credit) @@ -81,7 +80,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod // get tracks tracks, err := GetReleaseTracks(db, release.ID) if err != nil { - return nil, errors.New(fmt.Sprintf("Tracks: %s", err)) + return nil, fmt.Errorf("Tracks: %s", err) } for _, track := range tracks { release.Tracks = append(release.Tracks, track) @@ -90,7 +89,7 @@ func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*mod // get links links, err := GetReleaseLinks(db, release.ID) if err != nil { - return nil, errors.New(fmt.Sprintf("Links: %s", err)) + return nil, fmt.Errorf("Links: %s", err) } for _, link := range links { release.Links = append(release.Links, link) diff --git a/controller/session.go b/controller/session.go index 5028789..dfae551 100644 --- a/controller/session.go +++ b/controller/session.go @@ -2,7 +2,6 @@ package controller import ( "database/sql" - "errors" "fmt" "net/http" "strings" @@ -19,7 +18,7 @@ const TOKEN_LEN = 64 func GetSessionFromRequest(app *model.AppState, 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)) + return nil, fmt.Errorf("Failed to retrieve session cookie: %v", err) } var session *model.Session @@ -29,7 +28,7 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session session, err = GetSession(app.DB, sessionCookie.Value) if err != nil && !strings.Contains(err.Error(), "no rows") { - return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v", err)) + return nil, fmt.Errorf("Failed to retrieve session: %v", err) } if session != nil { From 028ed6029354a394e68b602aac8635dd3f24702c Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 30 Sep 2025 22:34:46 +0100 Subject: [PATCH 07/27] oops --- controller/controller.go | 2 +- controller/migrator.go | 6 +----- controller/schema-migration/004-test.sql | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 controller/schema-migration/004-test.sql diff --git a/controller/controller.go b/controller/controller.go index 44194e4..95615fb 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -5,7 +5,7 @@ import "math/rand" func GenerateAlnumString(length int) []byte { const CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" res := []byte{} - for i := 0; i < length; i++ { + for range length { res = append(res, CHARS[rand.Intn(len(CHARS))]) } return res diff --git a/controller/migrator.go b/controller/migrator.go index 1a70e62..3be8c21 100644 --- a/controller/migrator.go +++ b/controller/migrator.go @@ -9,7 +9,7 @@ import ( "github.com/jmoiron/sqlx" ) -const DB_VERSION int = 5 +const DB_VERSION int = 4 func CheckDBVersionAndMigrate(db *sqlx.DB) { db.MustExec("CREATE SCHEMA IF NOT EXISTS arimelody") @@ -50,10 +50,6 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) { ApplyMigration(db, "003-fail-lock") oldDBVersion = 4 - case 4: - ApplyMigration(db, "004-test") - oldDBVersion = 5 - } } diff --git a/controller/schema-migration/004-test.sql b/controller/schema-migration/004-test.sql deleted file mode 100644 index 3733de2..0000000 --- a/controller/schema-migration/004-test.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO arimelody.auditlog (level, type, content) VALUES (0, "test", "this is a db schema migration test!"); From 13a84f7f25b8208bcccf40faf1208e27a964c5cd Mon Sep 17 00:00:00 2001 From: ari melody Date: Sun, 19 Oct 2025 05:01:55 +0100 Subject: [PATCH 08/27] admin dashboard early UI refresh --- .air.toml | 4 +- admin/static/admin.css | 160 ++++++++++++++++++++--------- admin/static/admin.js | 17 +++ admin/static/edit-artist.css | 18 ++-- admin/static/edit-release.css | 67 +++++++----- admin/static/edit-track.css | 9 +- admin/static/index.css | 11 +- admin/static/index.js | 8 ++ admin/static/logs.css | 42 +++++--- admin/static/release-list-item.css | 43 +++----- admin/templates/html/layout.html | 7 +- admin/templates/html/logs.html | 2 +- 12 files changed, 249 insertions(+), 139 deletions(-) diff --git a/.air.toml b/.air.toml index 070166a..c6d499b 100644 --- a/.air.toml +++ b/.air.toml @@ -7,14 +7,14 @@ tmp_dir = "tmp" bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 - exclude_dir = ["admin/static", "admin\\static", "public", "uploads", "test", "db", "res"] + exclude_dir = ["uploads", "test", "db", "res"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html"] + include_ext = ["go", "tpl", "tmpl", "html", "css"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/admin/static/admin.css b/admin/static/admin.css index 877b5da..1f5a1fb 100644 --- a/admin/static/admin.css +++ b/admin/static/admin.css @@ -1,6 +1,71 @@ @import url("/style/prideflag.css"); @import url("/font/inter/inter.css"); +:root { + --bg-0: #101010; + --bg-1: #141414; + --bg-2: #181818; + --bg-3: #202020; + + --fg-0: #b0b0b0; + --fg-1: #c0c0c0; + --fg-2: #d0d0d0; + --fg-3: #e0e0e0; + + --col-shadow-0: #0002; + --col-shadow-1: #0004; + --col-shadow-2: #0006; + --col-highlight-0: #ffffff08; + --col-highlight-1: #fff1; + --col-highlight-2: #fff2; + + --col-new: #b3ee5b; + --col-on-new: #1b2013; + --col-save: #6fd7ff; + --col-on-save: #283f48; + --col-delete: #ff7171; + --col-on-delete: #371919; + + --col-warn: #ffe86a; + --col-on-warn: var(--bg-0); + --col-warn-hover: #ffec81; + + --shadow-sm: + 0 1px 2px var(--col-shadow-2), + inset 0 1px 1px var(--col-highlight-2); + --shadow-md: + 0 2px 4px var(--col-shadow-1), + inset 0 2px 2px var(--col-highlight-1); + --shadow-lg: + 0 4px 8px var(--col-shadow-0), + inset 0 4px 4px var(--col-highlight-0); +} + +@media (prefers-color-scheme: light) { + :root { + --bg-0: #e8e8e8; + --bg-1: #f0f0f0; + --bg-2: #f8f8f8; + --bg-3: #ffffff; + + --fg-0: #606060; + --fg-1: #404040; + --fg-2: #303030; + --fg-3: #202020; + + --col-shadow-0: #0002; + --col-shadow-1: #0004; + --col-shadow-2: #0008; + --col-highlight-0: #fff2; + --col-highlight-1: #fff4; + --col-highlight-2: #fff8; + + --col-warn: #ffe86a; + --col-on-warn: var(--fg-3); + --col-warn-hover: #ffec81; + } +} + body { width: 100%; height: calc(100vh - 1em); @@ -11,8 +76,12 @@ body { font-family: "Inter", sans-serif; font-size: 16px; - color: #303030; - background: #f0f0f0; + color: var(--fg-0); + background: var(--bg-0); +} + +h1, h2, h3, h4, h5, h6 { + color: var(--fg-3); } nav { @@ -22,40 +91,35 @@ nav { display: flex; flex-direction: row; justify-content: left; - - background: #f8f8f8; - border-radius: 4px; - border: 1px solid #808080; + gap: .5em; + user-select: none; } nav .icon { height: 100%; + border-radius: 100%; + box-shadow: var(--shadow-sm); + overflow: hidden; } -nav .title { - width: auto; +nav .icon img { + width: 100%; height: 100%; - - margin: 0 1em 0 0; - - display: flex; - - line-height: 2em; - text-decoration: none; - - color: inherit; } .nav-item { width: auto; height: 100%; - - margin: 0px; padding: 0 1em; - display: flex; + color: var(--fg-2); + background: var(--bg-2); + border-radius: 10em; + box-shadow: var(--shadow-sm); + line-height: 2em; + font-weight: 500; } .nav-item:hover { - background: #00000010; + background: var(--bg-1); text-decoration: none; } nav a { @@ -77,9 +141,11 @@ a { text-decoration: none; } +/* a:hover { text-decoration: underline; } +*/ a img.icon { height: .8em; @@ -133,17 +199,14 @@ code { #error { margin: 0 0 1em 0; padding: 1em; - border-radius: 4px; + border-radius: 8px; background: #ffffff; - border: 1px solid #888; } #message { background: #a9dfff; - border-color: #599fdc; } #error { background: #ffa9b8; - border-color: #dc5959; } @@ -152,52 +215,54 @@ a.delete:not(.button) { color: #d22828; } -button, .button { +.button, button { padding: .5em .8em; font-family: inherit; font-size: inherit; - border-radius: 4px; - border: 1px solid #a0a0a0; - background: #f0f0f0; + color: inherit; + background: var(--bg-2); + border: none; + border-radius: 10em; + box-shadow: var(--shadow-sm); + font-weight: 500; + transition: background .1s ease-out, color .1s ease-out; + + cursor: pointer; + user-select: none; } button:hover, .button:hover { background: #fff; - border-color: #d0d0d0; } button:active, .button:active { background: #d0d0d0; - border-color: #808080; } -.button, button { - color: inherit; -} .button.new, button.new { - background: #c4ff6a; - border-color: #84b141; + color: var(--col-on-new); + background: var(--col-new); } .button.save, button.save { - background: #6fd7ff; - border-color: #6f9eb0; + color: var(--col-on-save); + background: var(--col-save); } .button.delete, button.delete { - background: #ff7171; - border-color: #7d3535; + color: var(--col-on-delete); + background: var(--col-delete); } .button:hover, button:hover { - background: #fff; - border-color: #d0d0d0; + color: var(--bg-3); + background: var(--fg-3); } .button:active, button:active { - background: #d0d0d0; - border-color: #808080; + color: var(--bg-2); + background: var(--fg-0); } .button[disabled], button[disabled] { - background: #d0d0d0 !important; - border-color: #808080 !important; + color: var(--fg-0) !important; + background: var(--bg-3) !important; opacity: .5; - cursor: not-allowed !important; + cursor: default !important; } @@ -217,7 +282,6 @@ form input { padding: .3rem .5rem; display: block; border-radius: 4px; - border: 1px solid #808080; font-size: inherit; font-family: inherit; color: inherit; diff --git a/admin/static/admin.js b/admin/static/admin.js index 0763ab7..140efe0 100644 --- a/admin/static/admin.js +++ b/admin/static/admin.js @@ -69,3 +69,20 @@ export function makeMagicList(container, itemSelector, callback) { if (callback) callback(); }); } + +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, + })); + }); +} diff --git a/admin/static/edit-artist.css b/admin/static/edit-artist.css index 5627e64..1bab082 100644 --- a/admin/static/edit-artist.css +++ b/admin/static/edit-artist.css @@ -9,9 +9,9 @@ h1 { flex-direction: row; gap: 1.2em; - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .artist-avatar { @@ -27,6 +27,7 @@ h1 { cursor: pointer; } .artist-avatar #remove-avatar { + margin-top: .5em; padding: .3em .4em; } @@ -53,8 +54,8 @@ input[type="text"] { font-family: inherit; font-weight: inherit; color: inherit; - background: #ffffff; - border: 1px solid transparent; + background: var(--bg-0); + border: none; border-radius: 4px; outline: none; } @@ -85,9 +86,10 @@ input[type="text"]:focus { flex-direction: row; gap: 1em; align-items: center; - background: #f8f8f8; - border-radius: 8px; - border: 1px solid #808080; + + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .release-artwork { diff --git a/admin/static/edit-release.css b/admin/static/edit-release.css index aa70e34..d30db84 100644 --- a/admin/static/edit-release.css +++ b/admin/static/edit-release.css @@ -12,8 +12,8 @@ input[type="text"] { gap: 1.2em; border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .release-artwork { @@ -29,7 +29,8 @@ input[type="text"] { cursor: pointer; } .release-artwork #remove-artwork { - padding: .3em .4em; + margin-top: .5em; + padding: .3em .6em; } .release-info { @@ -54,17 +55,17 @@ input[type="text"] { background: transparent; outline: none; cursor: pointer; + transition: background .1s ease-out, border-color .1s ease-out; } #title:hover { - background: #ffffff; - border-color: #80808080; + background: var(--bg-3); + border-color: var(--fg-0); } #title:active, #title:focus { - background: #ffffff; - border-color: #808080; + background: var(--bg-3); } .release-title small { @@ -75,19 +76,21 @@ input[type="text"] { width: 100%; margin: .5em 0; border-collapse: collapse; + color: var(--fg-2); } .release-info table td { padding: .2em; - border-bottom: 1px solid #d0d0d0; + border-bottom: 1px solid color-mix(in srgb, var(--fg-0) 25%, transparent); + transition: background .1s ease-out, border-color .1s ease-out; } .release-info table tr td:first-child { vertical-align: top; - opacity: .66; + opacity: .5; } .release-info table tr td:not(:first-child) select:hover, .release-info table tr td:not(:first-child) input:hover, .release-info table tr td:not(:first-child) textarea:hover { - background: #e8e8e8; + background: var(--bg-3); cursor: pointer; } .release-info table td select, @@ -117,11 +120,19 @@ input[type="text"] { justify-content: right; } +.release-actions button, +.release-actions .button { + color: var(--fg-2); + background: var(--bg-3); +} + dialog { width: min(720px, calc(100% - 2em)); padding: 2em; border: 1px solid #101010; - border-radius: 8px; + border-radius: 16px; + color: var(--fg-0); + background: var(--bg-0); } dialog header { @@ -160,9 +171,9 @@ dialog div.dialog-actions { align-items: center; gap: 1em; - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .card.credits .credit p { @@ -170,10 +181,11 @@ dialog div.dialog-actions { } .card.credits .credit .artist-avatar { - border-radius: 8px; + border-radius: 12px; } .card.credits .credit .artist-name { + color: var(--fg-3); font-weight: bold; } @@ -197,8 +209,8 @@ dialog div.dialog-actions { gap: 1em; border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + background: var(--bg-2); + box-shadow: var(--shadow-md); } #editcredits .credit { @@ -232,6 +244,7 @@ dialog div.dialog-actions { margin: 0; display: flex; align-items: center; + color: inherit; } #editcredits .credit .credit-info .credit-attribute input[type="text"] { @@ -239,15 +252,17 @@ dialog div.dialog-actions { padding: .2em .4em; flex-grow: 1; font-family: inherit; - border: 1px solid #8888; + border: none; border-radius: 4px; - color: inherit; + color: var(--fg-2); + background: var(--bg-0); } #editcredits .credit .credit-info .credit-attribute input[type="checkbox"] { margin: 0 .3em; } #editcredits .credit .artist-name { + color: var(--fg-2); font-weight: bold; } @@ -256,8 +271,12 @@ dialog div.dialog-actions { opacity: .66; } -#editcredits .credit button.delete { - margin-left: auto; +#editcredits .credit .delete { + margin-right: .5em; + cursor: pointer; +} +#editcredits .credit .delete:hover { + text-decoration: underline; } #addcredit ul { @@ -400,9 +419,9 @@ dialog div.dialog-actions { flex-direction: column; gap: .5em; - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .card.tracks .track h3, diff --git a/admin/static/edit-track.css b/admin/static/edit-track.css index 600b680..1a9323f 100644 --- a/admin/static/edit-track.css +++ b/admin/static/edit-track.css @@ -11,9 +11,9 @@ h1 { flex-direction: row; gap: 1.2em; - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .track-info { @@ -49,7 +49,8 @@ h1 { font-weight: inherit; font-family: inherit; font-size: inherit; - border: 1px solid transparent; + background: var(--bg-0); + border: none; border-radius: 4px; outline: none; color: inherit; diff --git a/admin/static/index.css b/admin/static/index.css index 9fcd731..278224e 100644 --- a/admin/static/index.css +++ b/admin/static/index.css @@ -7,13 +7,18 @@ flex-direction: row; align-items: center; gap: .5em; + color: var(--fg-3); - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); + + transition: background .1s ease-out; + cursor: pointer; } .artist:hover { + background: var(--bg-1); text-decoration: hover; } diff --git a/admin/static/index.js b/admin/static/index.js index e251802..0091fa4 100644 --- a/admin/static/index.js +++ b/admin/static/index.js @@ -1,3 +1,5 @@ +import { hijackClickEvent } from "./admin.js"; + const newReleaseBtn = document.getElementById("create-release"); const newArtistBtn = document.getElementById("create-artist"); const newTrackBtn = document.getElementById("create-track"); @@ -72,3 +74,9 @@ newTrackBtn.addEventListener("click", event => { console.error(err); }); }); + +document.addEventListener("readystatechange", () => { + document.querySelectorAll(".card.artists .artist").forEach(el => { + hijackClickEvent(el, el.querySelector("a.artist-name")) + }); +}); diff --git a/admin/static/logs.css b/admin/static/logs.css index f0df299..4a66038 100644 --- a/admin/static/logs.css +++ b/admin/static/logs.css @@ -2,8 +2,14 @@ main { width: min(1080px, calc(100% - 2em))!important } -form { +form#search-form { + width: calc(100% - 2em); margin: 1em 0; + padding: 1em; + border-radius: 16px; + color: var(--fg-0); + background: var(--bg-2); + box-shadow: var(--shadow-md); } div#search { @@ -12,24 +18,25 @@ div#search { #search input { margin: 0; + padding: .3em .8em; flex-grow: 1; - - border-right: none; - border-top-right-radius: 0; - border-bottom-right-radius: 0; + border: none; + border-radius: 16px; + color: var(--fg-1); + background: var(--bg-0); + box-shadow: var(--shadow-sm); } #search button { - padding: 0 .5em; - - border-top-left-radius: 0; - border-bottom-left-radius: 0; + margin-left: .5em; + padding: 0 .8em; } form #filters p { margin: .5em 0 0 0; } form #filters label { + color: inherit; display: inline; } form #filters input { @@ -57,6 +64,10 @@ form #filters input { padding: .4em .8em; } +#logs .log { + color: var(--fg-2); +} + td, th { width: 1%; text-align: left; @@ -74,13 +85,14 @@ td.log-content { white-space: collapse; } -.log:hover { - background: #fff8; +#logs .log:hover { + background: color-mix(in srgb, var(--fg-3) 10%, transparent); } -.log.warn { - background: #ffe86a; +#logs .log.warn { + color: var(--col-on-warn); + background: var(--col-warn); } -.log.warn:hover { - background: #ffec81; +#logs .log.warn:hover { + background: var(--col-warn-hover); } diff --git a/admin/static/release-list-item.css b/admin/static/release-list-item.css index 638eac0..fb5d2d4 100644 --- a/admin/static/release-list-item.css +++ b/admin/static/release-list-item.css @@ -5,9 +5,9 @@ flex-direction: row; gap: 1em; - border-radius: 8px; - background: #f8f8f8f8; - border: 1px solid #808080; + border-radius: 16px; + background: var(--bg-2); + box-shadow: var(--shadow-md); } .release h3, @@ -16,11 +16,15 @@ } .release-artwork { + margin: auto 0; width: 96px; display: flex; justify-content: center; align-items: center; + border-radius: 4px; + overflow: hidden; + box-shadow: var(--shadow-sm); } .release-artwork img { @@ -42,30 +46,9 @@ gap: .5em; } -.release-links li { - flex-grow: 1; -} - -.release-links a { - padding: .5em; - display: block; - - border-radius: 8px; - text-decoration: none; - color: #f0f0f0; - background: #303030; - text-align: center; - - transition: color .1s, background .1s; -} - -.release-links a:hover { - color: #303030; - background: #f0f0f0; -} - .release-actions { margin-top: .5em; + user-select: none; } .release-actions a { @@ -74,14 +57,14 @@ display: inline-block; border-radius: 4px; - background: #e0e0e0; + background: var(--bg-3); + box-shadow: var(--shadow-sm); - transition: color .1s, background .1s; + transition: color .1s ease-out, background .1s ease-out; } .release-actions a:hover { - color: #303030; - background: #f0f0f0; - + background: var(--bg-0); + color: var(--fg-3); text-decoration: none; } diff --git a/admin/templates/html/layout.html b/admin/templates/html/layout.html index fdeda9b..4925ce9 100644 --- a/admin/templates/html/layout.html +++ b/admin/templates/html/layout.html @@ -16,10 +16,9 @@