diff --git a/.air.toml b/.air.toml index c6d499b..070166a 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 = ["uploads", "test", "db", "res"] + exclude_dir = ["admin/static", "admin\\static", "public", "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", "css"] + include_ext = ["go", "tpl", "tmpl", "html"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/.forgejo/workflows/push-prod.yaml b/.forgejo/workflows/push-prod.yaml deleted file mode 100644 index 7a9fd05..0000000 --- a/.forgejo/workflows/push-prod.yaml +++ /dev/null @@ -1,50 +0,0 @@ -on: - push: - branches: - - main - -env: - EXEC: arimelody-web - REMOTE: ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} - PORT: ${{ secrets.SSH_PORT }} - -jobs: - deploy: - runs-on: docker - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '^1.25.1' - - - name: Run tests - run: go test -v ./model - - - name: Build binary - run: make build - - - name: Bundle tarball - run: make bundle - - - name: Set up SSH keys - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Copy to production server - run: | - ssh-keyscan -p $PORT ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts - scp -P $PORT ./$EXEC.tar.gz $REMOTE:~/ - - - name: Restart production - run: | - ssh -o StrictHostKeyChecking=no $REMOTE -p $PORT << EOT - cd ${{ secrets.DEPLOY_DIR }} - tar xzf ~/$EXEC.tar.gz - /bin/bash ~/restart.sh - rm ~/$EXEC.tar.gz - EOT - diff --git a/Makefile b/Makefile index 98a4ea1..11e565a 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ EXEC = arimelody-web .PHONY: $(EXEC) -build: +$(EXEC): GOOS=linux GOARCH=amd64 go build -o $(EXEC) -bundle: build - tar czf $(EXEC).tar.gz --exclude ".DS_Store" $(EXEC) admin/static/ public/ +bundle: $(EXEC) + tar czf $(EXEC).tar.gz $(EXEC) admin/components/ admin/views/ admin/static/ views/ public/ schema-migration/ clean: rm $(EXEC) $(EXEC).tar.gz diff --git a/admin/account/accounthttp.go b/admin/account/accounthttp.go index d1b0d49..5601a2e 100644 --- a/admin/account/accounthttp.go +++ b/admin/account/accounthttp.go @@ -7,7 +7,6 @@ import ( "net/url" "os" - "arimelody-web/admin/core" "arimelody-web/admin/templates" "arimelody-web/controller" "arimelody-web/log" @@ -21,12 +20,12 @@ func Handler(app *model.AppState) http.Handler { mux.Handle("/", accountIndexHandler(app)) - mux.Handle("/account/totp-setup", totpSetupHandler(app)) - mux.Handle("/account/totp-confirm", totpConfirmHandler(app)) - mux.Handle("/account/totp-delete", http.StripPrefix("/totp-delete", totpDeleteHandler(app))) + mux.Handle("/totp-setup", totpSetupHandler(app)) + mux.Handle("/totp-confirm", totpConfirmHandler(app)) + mux.Handle("/totp-delete/", http.StripPrefix("/totp-delete", totpDeleteHandler(app))) - mux.Handle("/account/password", changePasswordHandler(app)) - mux.Handle("/account/delete", deleteAccountHandler(app)) + mux.Handle("/password", changePasswordHandler(app)) + mux.Handle("/delete", deleteAccountHandler(app)) return mux } @@ -48,7 +47,7 @@ func accountIndexHandler(app *model.AppState) http.Handler { } accountResponse struct { - core.AdminPageData + Session *model.Session TOTPs []TOTP } ) @@ -69,7 +68,7 @@ func accountIndexHandler(app *model.AppState) http.Handler { session.Error = sessionError err = templates.AccountTemplate.Execute(w, accountResponse{ - AdminPageData: core.AdminPageData{ Path: r.URL.Path, Session: session }, + Session: session, TOTPs: totps, }) if err != nil { @@ -173,7 +172,7 @@ func deleteAccountHandler(app *model.AppState) http.Handler { } type totpConfirmData struct { - core.AdminPageData + Session *model.Session TOTP *model.TOTP NameEscaped string QRBase64Image string @@ -182,9 +181,13 @@ type totpConfirmData struct { func totpSetupHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { + type totpSetupData struct { + Session *model.Session + } + session := r.Context().Value("session").(*model.Session) - err := templates.TOTPSetupTemplate.Execute(w, core.AdminPageData{ Path: "/account", 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,9 +224,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{ - AdminPageData: core.AdminPageData{ Path: r.URL.Path, 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) @@ -237,8 +238,8 @@ 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{ - AdminPageData: core.AdminPageData{ Path: r.URL.Path, Session: session }, + err = templates.TotpConfirmTemplate.Execute(w, totpConfirmData{ + Session: session, TOTP: &totp, NameEscaped: url.PathEscape(totp.Name), QRBase64Image: qrBase64Image, @@ -269,6 +270,11 @@ func totpConfirmHandler(app *model.AppState) http.Handler { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } + code := r.FormValue("totp") + if len(code) != controller.TOTP_CODE_LENGTH { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } totp, err := controller.GetTOTP(app.DB, session.Account.ID, name) if err != nil { @@ -288,22 +294,23 @@ func totpConfirmHandler(app *model.AppState) http.Handler { fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) } - code := r.FormValue("totp") confirmCode := controller.GenerateTOTP(totp.Secret, 0) - confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1) - if len(code) != controller.TOTP_CODE_LENGTH || (code != confirmCode && code != confirmCodeOffset) { - session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." } - err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ - AdminPageData: core.AdminPageData{ Path: r.URL.Path, Session: session }, - TOTP: totp, - NameEscaped: url.PathEscape(totp.Name), - QRBase64Image: qrBase64Image, - }) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to render TOTP setup page: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + if code != confirmCode { + 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{ + Session: session, + TOTP: totp, + NameEscaped: url.PathEscape(totp.Name), + QRBase64Image: qrBase64Image, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "WARN: Failed to render TOTP setup page: %v\n", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return } - return } err = controller.ConfirmTOTP(app.DB, session.Account.ID, name) @@ -324,23 +331,18 @@ func totpConfirmHandler(app *model.AppState) http.Handler { func totpDeleteHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { + if r.Method != http.MethodGet { http.NotFound(w, r) return } - session := r.Context().Value("session").(*model.Session) + if len(r.URL.Path) < 2 { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + name := r.URL.Path[1:] - err := r.ParseForm() - if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - name := r.FormValue("totp-name") - if len(name) == 0 { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } + session := r.Context().Value("session").(*model.Session) totp, err := controller.GetTOTP(app.DB, session.Account.ID, name) if err != nil { diff --git a/admin/auth/authhttp.go b/admin/auth/authhttp.go index 7805638..aba4074 100644 --- a/admin/auth/authhttp.go +++ b/admin/auth/authhttp.go @@ -1,7 +1,6 @@ package auth import ( - "arimelody-web/admin/core" "arimelody-web/admin/templates" "arimelody-web/controller" "arimelody-web/log" @@ -136,8 +135,12 @@ func LoginHandler(app *model.AppState) http.Handler { session := r.Context().Value("session").(*model.Session) + type loginData struct { + Session *model.Session + } + render := func() { - err := templates.LoginTemplate.Execute(w, core.AdminPageData{ 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) @@ -248,8 +251,12 @@ func LoginTOTPHandler(app *model.AppState) http.Handler { return } + type loginTOTPData struct { + Session *model.Session + } + render := func() { - err := templates.LoginTOTPTemplate.Execute(w, core.AdminPageData{ 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) diff --git a/admin/templates/html/components/credit/addcredit.html b/admin/components/credits/addcredit.html similarity index 94% rename from admin/templates/html/components/credit/addcredit.html rename to admin/components/credits/addcredit.html index 1650b86..debc660 100644 --- a/admin/templates/html/components/credit/addcredit.html +++ b/admin/components/credits/addcredit.html @@ -7,7 +7,7 @@ {{range $Artist := .Artists}}
{{.PrintArtists true true}}
{{.ReleaseType}} - ({{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}})
+ ({{len .Tracks}} track{{if not (eq (len .Tracks) 1)}}s{{end}})