diff --git a/Makefile b/Makefile index f96321c..11e565a 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ EXEC = arimelody-web .PHONY: $(EXEC) -build: +$(EXEC): GOOS=linux GOARCH=amd64 go build -o $(EXEC) -bundle: build +bundle: $(EXEC) 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 b2c3b0d..945a507 100644 --- a/admin/accounthttp.go +++ b/admin/accounthttp.go @@ -1,18 +1,17 @@ package admin import ( - "database/sql" - "fmt" - "net/http" - "net/url" - "os" + "database/sql" + "fmt" + "net/http" + "net/url" + "os" - "arimelody-web/admin/templates" - "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 accountHandler(app *model.AppState) http.Handler { @@ -65,7 +64,7 @@ func accountIndexHandler(app *model.AppState) http.Handler { session.Message = sessionMessage session.Error = sessionError - err = templates.AccountTemplate.Execute(w, accountResponse{ + err = accountTemplate.Execute(w, accountResponse{ Session: session, TOTPs: totps, }) @@ -185,7 +184,7 @@ func totpSetupHandler(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err := templates.TOTPSetupTemplate.Execute(w, totpSetupData{ Session: session }) + err := 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) @@ -222,7 +221,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 := templates.TOTPSetupTemplate.Execute(w, totpConfirmData{ Session: session }) + err := 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) @@ -236,7 +235,7 @@ func totpSetupHandler(app *model.AppState) http.Handler { fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) } - err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ + err = totpConfirmTemplate.Execute(w, totpConfirmData{ Session: session, TOTP: &totp, NameEscaped: url.PathEscape(totp.Name), @@ -297,7 +296,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 = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ + err = totpConfirmTemplate.Execute(w, totpConfirmData{ Session: session, TOTP: totp, NameEscaped: url.PathEscape(totp.Name), diff --git a/admin/artisthttp.go b/admin/artisthttp.go index 67ea7d2..9fa6bb2 100644 --- a/admin/artisthttp.go +++ b/admin/artisthttp.go @@ -1,13 +1,12 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/admin/templates" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/model" + "arimelody-web/controller" ) func serveArtist(app *model.AppState) http.Handler { @@ -40,7 +39,7 @@ func serveArtist(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err = templates.EditArtistTemplate.Execute(w, ArtistResponse{ + err = artistTemplate.Execute(w, ArtistResponse{ Session: session, Artist: artist, Credits: credits, diff --git a/admin/templates/html/components/credits/addcredit.html b/admin/components/credits/addcredit.html similarity index 100% rename from admin/templates/html/components/credits/addcredit.html rename to admin/components/credits/addcredit.html diff --git a/admin/templates/html/components/credits/editcredits.html b/admin/components/credits/editcredits.html similarity index 100% rename from admin/templates/html/components/credits/editcredits.html rename to admin/components/credits/editcredits.html diff --git a/admin/templates/html/components/credits/newcredit.html b/admin/components/credits/newcredit.html similarity index 100% rename from admin/templates/html/components/credits/newcredit.html rename to admin/components/credits/newcredit.html diff --git a/admin/templates/html/components/links/editlinks.html b/admin/components/links/editlinks.html similarity index 100% rename from admin/templates/html/components/links/editlinks.html rename to admin/components/links/editlinks.html diff --git a/admin/templates/html/components/release/release-list-item.html b/admin/components/release/release-list-item.html similarity index 100% rename from admin/templates/html/components/release/release-list-item.html rename to admin/components/release/release-list-item.html diff --git a/admin/templates/html/components/tracks/addtrack.html b/admin/components/tracks/addtrack.html similarity index 100% rename from admin/templates/html/components/tracks/addtrack.html rename to admin/components/tracks/addtrack.html diff --git a/admin/templates/html/components/tracks/edittracks.html b/admin/components/tracks/edittracks.html similarity index 100% rename from admin/templates/html/components/tracks/edittracks.html rename to admin/components/tracks/edittracks.html diff --git a/admin/templates/html/components/tracks/newtrack.html b/admin/components/tracks/newtrack.html similarity index 100% rename from admin/templates/html/components/tracks/newtrack.html rename to admin/components/tracks/newtrack.html diff --git a/admin/http.go b/admin/http.go index 05e181d..245a152 100644 --- a/admin/http.go +++ b/admin/http.go @@ -1,24 +1,20 @@ package admin import ( - "context" - "database/sql" - "embed" - "fmt" - "mime" - "net/http" - "os" - "path" - "path/filepath" - "strings" - "time" + "context" + "database/sql" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" - "arimelody-web/admin/templates" - "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 { @@ -95,7 +91,7 @@ func AdminIndexHandler(app *model.AppState) http.Handler { Tracks []*model.Track } - err = templates.IndexTemplate.Execute(w, IndexData{ + err = indexTemplate.Execute(w, IndexData{ Session: session, Releases: releases, Artists: artists, @@ -124,7 +120,7 @@ func registerAccountHandler(app *model.AppState) http.Handler { } render := func() { - err := templates.RegisterTemplate.Execute(w, registerData{ Session: session }) + err := 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) @@ -234,7 +230,7 @@ func loginHandler(app *model.AppState) http.Handler { } render := func() { - err := templates.LoginTemplate.Execute(w, loginData{ Session: session }) + err := 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) @@ -350,7 +346,7 @@ func loginTOTPHandler(app *model.AppState) http.Handler { } render := func() { - err := templates.LoginTOTPTemplate.Execute(w, loginTOTPData{ Session: session }) + err := 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) @@ -444,7 +440,7 @@ func logoutHandler(app *model.AppState) http.Handler { Path: "/", }) - err = templates.LogoutTemplate.Execute(w, nil) + err = 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) @@ -464,21 +460,24 @@ 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) { - file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(r.URL.Path))) + info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path))) + // does the file exist? 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 } - w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path))) - w.WriteHeader(http.StatusOK) - - w.Write(file) + http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r) }) } diff --git a/admin/logshttp.go b/admin/logshttp.go index 99f0ef1..7249b16 100644 --- a/admin/logshttp.go +++ b/admin/logshttp.go @@ -1,13 +1,12 @@ package admin import ( - "arimelody-web/admin/templates" - "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 { @@ -55,7 +54,7 @@ func logsHandler(app *model.AppState) http.Handler { Logs []*log.Log } - err = templates.LogsTemplate.Execute(w, LogsResponse{ + err = logsTemplate.Execute(w, LogsResponse{ Session: session, Logs: logs, }) diff --git a/admin/releasehttp.go b/admin/releasehttp.go index 30c967b..c6b68ab 100644 --- a/admin/releasehttp.go +++ b/admin/releasehttp.go @@ -1,13 +1,12 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/admin/templates" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/model" ) func serveRelease(app *model.AppState) http.Handler { @@ -61,7 +60,7 @@ func serveRelease(app *model.AppState) http.Handler { Release *model.Release } - err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{ + err = releaseTemplate.Execute(w, ReleaseResponse{ Session: session, Release: release, }) @@ -75,7 +74,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 := templates.EditCreditsTemplate.Execute(w, release) + err := 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) @@ -98,7 +97,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = templates.AddCreditTemplate.Execute(w, response{ + err = addCreditTemplate.Execute(w, response{ ReleaseID: release.ID, Artists: artists, }) @@ -124,7 +123,7 @@ func serveNewCredit(app *model.AppState) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = templates.NewCreditTemplate.Execute(w, artist) + err = 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) @@ -135,7 +134,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 := templates.EditCreditsTemplate.Execute(w, release) + err := editLinksTemplate.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) @@ -152,7 +151,7 @@ func serveEditTracks(release *model.Release) http.Handler { Add func(a int, b int) int } - err := templates.EditTracksTemplate.Execute(w, editTracksData{ + err := editTracksTemplate.Execute(w, editTracksData{ Release: release, Add: func(a, b int) int { return a + b }, }) @@ -178,7 +177,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = templates.AddTrackTemplate.Execute(w, response{ + err = addTrackTemplate.Execute(w, response{ ReleaseID: release.ID, Tracks: tracks, }) @@ -186,6 +185,7 @@ 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,10 +204,11 @@ func serveNewTrack(app *model.AppState) http.Handler { } w.Header().Set("Content-Type", "text/html") - err = templates.NewTrackTemplate.Execute(w, track) + err = 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 new file mode 100644 index 0000000..606d569 --- /dev/null +++ b/admin/templates.go @@ -0,0 +1,125 @@ +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/templates/templates.go b/admin/templates/templates.go deleted file mode 100644 index 58cd1d0..0000000 --- a/admin/templates/templates.go +++ /dev/null @@ -1,128 +0,0 @@ -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 e93d1bb..93eacdb 100644 --- a/admin/trackhttp.go +++ b/admin/trackhttp.go @@ -1,13 +1,12 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/admin/templates" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/model" + "arimelody-web/controller" ) func serveTrack(app *model.AppState) http.Handler { @@ -40,7 +39,7 @@ func serveTrack(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) - err = templates.EditTrackTemplate.Execute(w, TrackResponse{ + err = trackTemplate.Execute(w, TrackResponse{ Session: session, Track: track, Releases: releases, diff --git a/admin/templates/html/edit-account.html b/admin/views/edit-account.html similarity index 100% rename from admin/templates/html/edit-account.html rename to admin/views/edit-account.html diff --git a/admin/templates/html/edit-artist.html b/admin/views/edit-artist.html similarity index 100% rename from admin/templates/html/edit-artist.html rename to admin/views/edit-artist.html diff --git a/admin/templates/html/edit-release.html b/admin/views/edit-release.html similarity index 100% rename from admin/templates/html/edit-release.html rename to admin/views/edit-release.html diff --git a/admin/templates/html/edit-track.html b/admin/views/edit-track.html similarity index 100% rename from admin/templates/html/edit-track.html rename to admin/views/edit-track.html diff --git a/admin/templates/html/index.html b/admin/views/index.html similarity index 100% rename from admin/templates/html/index.html rename to admin/views/index.html diff --git a/admin/templates/html/layout.html b/admin/views/layout.html similarity index 100% rename from admin/templates/html/layout.html rename to admin/views/layout.html diff --git a/admin/templates/html/login-totp.html b/admin/views/login-totp.html similarity index 100% rename from admin/templates/html/login-totp.html rename to admin/views/login-totp.html diff --git a/admin/templates/html/login.html b/admin/views/login.html similarity index 100% rename from admin/templates/html/login.html rename to admin/views/login.html diff --git a/admin/templates/html/logout.html b/admin/views/logout.html similarity index 100% rename from admin/templates/html/logout.html rename to admin/views/logout.html diff --git a/admin/templates/html/logs.html b/admin/views/logs.html similarity index 100% rename from admin/templates/html/logs.html rename to admin/views/logs.html diff --git a/admin/templates/html/register.html b/admin/views/register.html similarity index 100% rename from admin/templates/html/register.html rename to admin/views/register.html diff --git a/admin/templates/html/totp-confirm.html b/admin/views/totp-confirm.html similarity index 100% rename from admin/templates/html/totp-confirm.html rename to admin/views/totp-confirm.html diff --git a/admin/templates/html/totp-setup.html b/admin/views/totp-setup.html similarity index 100% rename from admin/templates/html/totp-setup.html rename to admin/views/totp-setup.html diff --git a/api/api.go b/api/api.go index c5156c2..398db4b 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "context" + "errors" "fmt" "net/http" "os" @@ -172,7 +173,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, fmt.Errorf("Failed to retrieve session cookie: %v\n", err) + return nil, errors.New(fmt.Sprintf("Failed to retrieve session cookie: %v\n", err)) } if sessionCookie != nil { token = sessionCookie.Value @@ -187,7 +188,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, fmt.Errorf("Failed to retrieve session: %v\n", err) + return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v\n", err)) } if session != nil { diff --git a/api/uploads.go b/api/uploads.go index 3c3c58a..4678f22 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\" created.", imagePath) + app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext) return filename, nil } diff --git a/controller/controller.go b/controller/controller.go index 95615fb..44194e4 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 range length { + for i := 0; i < length; i++ { res = append(res, CHARS[rand.Intn(len(CHARS))]) } return res diff --git a/controller/migrator.go b/controller/migrator.go index 3be8c21..4b99b9c 100644 --- a/controller/migrator.go +++ b/controller/migrator.go @@ -1,12 +1,11 @@ package controller import ( - "embed" - "fmt" - "os" - "time" + "fmt" + "os" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) const DB_VERSION int = 4 @@ -56,13 +55,10 @@ func CheckDBVersionAndMigrate(db *sqlx.DB) { 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 := schemaFS.ReadFile("schema-migration/" + scriptFile + ".sql") + bytes, err := os.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/controller/qr.go b/controller/qr.go index 21ed3e6..dd08637 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,35 +18,36 @@ 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) +} diff --git a/controller/release.go b/controller/release.go index afd09fb..3dcad26 100644 --- a/controller/release.go +++ b/controller/release.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "fmt" "arimelody-web/model" @@ -20,7 +21,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, fmt.Errorf("Credits: %s", err) + return nil, errors.New(fmt.Sprintf("Credits: %s", err)) } for _, credit := range credits { release.Credits = append(release.Credits, credit) @@ -29,7 +30,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, fmt.Errorf("Tracks: %s", err) + return nil, errors.New(fmt.Sprintf("Tracks: %s", err)) } for _, track := range tracks { release.Tracks = append(release.Tracks, track) @@ -38,7 +39,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, fmt.Errorf("Links: %s", err) + return nil, errors.New(fmt.Sprintf("Links: %s", err)) } for _, link := range links { release.Links = append(release.Links, link) @@ -70,7 +71,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, fmt.Errorf("Credits: %s", err) + return nil, errors.New(fmt.Sprintf("Credits: %s", err)) } for _, credit := range credits { release.Credits = append(release.Credits, credit) @@ -80,7 +81,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, fmt.Errorf("Tracks: %s", err) + return nil, errors.New(fmt.Sprintf("Tracks: %s", err)) } for _, track := range tracks { release.Tracks = append(release.Tracks, track) @@ -89,7 +90,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, fmt.Errorf("Links: %s", err) + return nil, errors.New(fmt.Sprintf("Links: %s", err)) } for _, link := range links { release.Links = append(release.Links, link) diff --git a/controller/session.go b/controller/session.go index dfae551..5028789 100644 --- a/controller/session.go +++ b/controller/session.go @@ -2,6 +2,7 @@ package controller import ( "database/sql" + "errors" "fmt" "net/http" "strings" @@ -18,7 +19,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, fmt.Errorf("Failed to retrieve session cookie: %v", err) + return nil, errors.New(fmt.Sprintf("Failed to retrieve session cookie: %v", err)) } var session *model.Session @@ -28,7 +29,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, fmt.Errorf("Failed to retrieve session: %v", err) + return nil, errors.New(fmt.Sprintf("Failed to retrieve session: %v", err)) } if session != nil { diff --git a/main.go b/main.go index 553f109..eaed7c6 100644 --- a/main.go +++ b/main.go @@ -1,33 +1,32 @@ package main import ( - "bufio" - "embed" - "errors" - "fmt" - stdLog "log" - "math" - "math/rand" - "net" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "time" + "bufio" + "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 @@ -36,16 +35,12 @@ 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 @@ -531,7 +526,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.ServeFiles(filepath.Join(app.Config.DataDirectory, "uploads")))) + mux.Handle("/uploads/", http.StripPrefix("/uploads", view.StaticHandler(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 1a13be9..3a1c230 100644 --- a/model/appstate.go +++ b/model/appstate.go @@ -1,11 +1,9 @@ package model import ( - "embed" + "github.com/jmoiron/sqlx" - "github.com/jmoiron/sqlx" - - "arimelody-web/log" + "arimelody-web/log" ) type ( @@ -45,6 +43,5 @@ type ( Config Config Log log.Logger Twitch *TwitchState - PublicFS embed.FS } ) diff --git a/controller/schema-migration/000-init.sql b/schema-migration/000-init.sql similarity index 100% rename from controller/schema-migration/000-init.sql rename to schema-migration/000-init.sql diff --git a/controller/schema-migration/001-pre-versioning.sql b/schema-migration/001-pre-versioning.sql similarity index 100% rename from controller/schema-migration/001-pre-versioning.sql rename to schema-migration/001-pre-versioning.sql diff --git a/controller/schema-migration/002-audit-logs.sql b/schema-migration/002-audit-logs.sql similarity index 100% rename from controller/schema-migration/002-audit-logs.sql rename to schema-migration/002-audit-logs.sql diff --git a/controller/schema-migration/003-fail-lock.sql b/schema-migration/003-fail-lock.sql similarity index 100% rename from controller/schema-migration/003-fail-lock.sql rename to schema-migration/003-fail-lock.sql diff --git a/templates/html/prideflag.html b/templates/html/prideflag.html deleted file mode 100644 index 47ce4c7..0000000 --- a/templates/html/prideflag.html +++ /dev/null @@ -1,21 +0,0 @@ -{{define "prideflag"}} - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/templates/templates.go b/templates/templates.go index c6ab41e..752c78d 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -1,36 +1,28 @@ package templates import ( - _ "embed" - "html/template" - "strings" + "html/template" + "path/filepath" ) -//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.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"), )) -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/index.go b/view/index.go index bcd0d06..b6e3891 100644 --- a/view/index.go +++ b/view/index.go @@ -40,6 +40,6 @@ func IndexHandler(app *model.AppState) http.Handler { return } - ServeEmbedFS(app.PublicFS, "public").ServeHTTP(w, r) + StaticHandler("public").ServeHTTP(w, r) }) } diff --git a/view/files.go b/view/static.go similarity index 54% rename from view/files.go rename to view/static.go index c1b0e29..52263a2 100644 --- a/view/files.go +++ b/view/static.go @@ -1,31 +1,13 @@ package view import ( - "embed" "errors" - "mime" "net/http" "os" - "path" "path/filepath" ) -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 { +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))) @@ -46,3 +28,4 @@ func ServeFiles(directory string) http.Handler { http.FileServer(http.Dir(directory)).ServeHTTP(w, r) }) } + diff --git a/templates/html/404.html b/views/404.html similarity index 100% rename from templates/html/404.html rename to views/404.html diff --git a/templates/html/footer.html b/views/footer.html similarity index 100% rename from templates/html/footer.html rename to views/footer.html diff --git a/templates/html/header.html b/views/header.html similarity index 100% rename from templates/html/header.html rename to views/header.html diff --git a/templates/html/index.html b/views/index.html similarity index 100% rename from templates/html/index.html rename to views/index.html diff --git a/templates/html/layout.html b/views/layout.html similarity index 100% rename from templates/html/layout.html rename to views/layout.html diff --git a/templates/html/music-gateway.html b/views/music-gateway.html similarity index 100% rename from templates/html/music-gateway.html rename to views/music-gateway.html diff --git a/templates/html/music.html b/views/music.html similarity index 100% rename from templates/html/music.html rename to views/music.html diff --git a/admin/templates/html/prideflag.html b/views/prideflag.html similarity index 100% rename from admin/templates/html/prideflag.html rename to views/prideflag.html