home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

embed html template and static files

+345 -284
+2 -2
Makefile
··· 2 2 3 3 .PHONY: $(EXEC) 4 4 5 - $(EXEC): 5 + build: 6 6 GOOS=linux GOARCH=amd64 go build -o $(EXEC) 7 7 8 - bundle: $(EXEC) 8 + bundle: build 9 9 tar czf $(EXEC).tar.gz $(EXEC) admin/components/ admin/views/ admin/static/ views/ public/ schema-migration/ 10 10 11 11 clean:
+15 -14
admin/accounthttp.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "database/sql" 5 - "fmt" 6 - "net/http" 7 - "net/url" 8 - "os" 4 + "database/sql" 5 + "fmt" 6 + "net/http" 7 + "net/url" 8 + "os" 9 9 10 - "arimelody-web/controller" 11 - "arimelody-web/log" 12 - "arimelody-web/model" 10 + "arimelody-web/admin/templates" 11 + "arimelody-web/controller" 12 + "arimelody-web/log" 13 + "arimelody-web/model" 13 14 14 - "golang.org/x/crypto/bcrypt" 15 + "golang.org/x/crypto/bcrypt" 15 16 ) 16 17 17 18 func accountHandler(app *model.AppState) http.Handler { ··· 64 65 session.Message = sessionMessage 65 66 session.Error = sessionError 66 67 67 - err = accountTemplate.Execute(w, accountResponse{ 68 + err = templates.AccountTemplate.Execute(w, accountResponse{ 68 69 Session: session, 69 70 TOTPs: totps, 70 71 }) ··· 184 185 185 186 session := r.Context().Value("session").(*model.Session) 186 187 187 - err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session }) 188 + err := templates.TOTPSetupTemplate.Execute(w, totpSetupData{ Session: session }) 188 189 if err != nil { 189 190 fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) 190 191 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 221 222 if err != nil { 222 223 fmt.Printf("WARN: Failed to create TOTP method: %s\n", err) 223 224 controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.") 224 - err := totpSetupTemplate.Execute(w, totpConfirmData{ Session: session }) 225 + err := templates.TOTPSetupTemplate.Execute(w, totpConfirmData{ Session: session }) 225 226 if err != nil { 226 227 fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err) 227 228 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 235 236 fmt.Fprintf(os.Stderr, "WARN: Failed to generate TOTP QR code: %v\n", err) 236 237 } 237 238 238 - err = totpConfirmTemplate.Execute(w, totpConfirmData{ 239 + err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ 239 240 Session: session, 240 241 TOTP: &totp, 241 242 NameEscaped: url.PathEscape(totp.Name), ··· 296 297 confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1) 297 298 if code != confirmCodeOffset { 298 299 session.Error = sql.NullString{ Valid: true, String: "Incorrect TOTP code. Please try again." } 299 - err = totpConfirmTemplate.Execute(w, totpConfirmData{ 300 + err = templates.TOTPConfirmTemplate.Execute(w, totpConfirmData{ 300 301 Session: session, 301 302 TOTP: totp, 302 303 NameEscaped: url.PathEscape(totp.Name),
+7 -6
admin/artisthttp.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "fmt" 5 - "net/http" 6 - "strings" 4 + "fmt" 5 + "net/http" 6 + "strings" 7 7 8 - "arimelody-web/model" 9 - "arimelody-web/controller" 8 + "arimelody-web/admin/templates" 9 + "arimelody-web/controller" 10 + "arimelody-web/model" 10 11 ) 11 12 12 13 func serveArtist(app *model.AppState) http.Handler { ··· 39 40 40 41 session := r.Context().Value("session").(*model.Session) 41 42 42 - err = artistTemplate.Execute(w, ArtistResponse{ 43 + err = templates.EditArtistTemplate.Execute(w, ArtistResponse{ 43 44 Session: session, 44 45 Artist: artist, 45 46 Credits: credits,
admin/components/credits/addcredit.html admin/templates/html/components/credits/addcredit.html
admin/components/credits/editcredits.html admin/templates/html/components/credits/editcredits.html
admin/components/credits/newcredit.html admin/templates/html/components/credits/newcredit.html
admin/components/links/editlinks.html admin/templates/html/components/links/editlinks.html
admin/components/release/release-list-item.html admin/templates/html/components/release/release-list-item.html
admin/components/tracks/addtrack.html admin/templates/html/components/tracks/addtrack.html
admin/components/tracks/edittracks.html admin/templates/html/components/tracks/edittracks.html
admin/components/tracks/newtrack.html admin/templates/html/components/tracks/newtrack.html
+29 -28
admin/http.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "context" 5 - "database/sql" 6 - "fmt" 7 - "net/http" 8 - "os" 9 - "path/filepath" 10 - "strings" 11 - "time" 4 + "context" 5 + "database/sql" 6 + "embed" 7 + "fmt" 8 + "mime" 9 + "net/http" 10 + "os" 11 + "path" 12 + "path/filepath" 13 + "strings" 14 + "time" 12 15 13 - "arimelody-web/controller" 14 - "arimelody-web/log" 15 - "arimelody-web/model" 16 + "arimelody-web/admin/templates" 17 + "arimelody-web/controller" 18 + "arimelody-web/log" 19 + "arimelody-web/model" 16 20 17 - "golang.org/x/crypto/bcrypt" 21 + "golang.org/x/crypto/bcrypt" 18 22 ) 19 23 20 24 func Handler(app *model.AppState) http.Handler { ··· 91 95 Tracks []*model.Track 92 96 } 93 97 94 - err = indexTemplate.Execute(w, IndexData{ 98 + err = templates.IndexTemplate.Execute(w, IndexData{ 95 99 Session: session, 96 100 Releases: releases, 97 101 Artists: artists, ··· 120 124 } 121 125 122 126 render := func() { 123 - err := registerTemplate.Execute(w, registerData{ Session: session }) 127 + err := templates.RegisterTemplate.Execute(w, registerData{ Session: session }) 124 128 if err != nil { 125 129 fmt.Printf("WARN: Error rendering create account page: %s\n", err) 126 130 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 230 234 } 231 235 232 236 render := func() { 233 - err := loginTemplate.Execute(w, loginData{ Session: session }) 237 + err := templates.LoginTemplate.Execute(w, loginData{ Session: session }) 234 238 if err != nil { 235 239 fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err) 236 240 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 346 350 } 347 351 348 352 render := func() { 349 - err := loginTOTPTemplate.Execute(w, loginTOTPData{ Session: session }) 353 + err := templates.LoginTOTPTemplate.Execute(w, loginTOTPData{ Session: session }) 350 354 if err != nil { 351 355 fmt.Fprintf(os.Stderr, "WARN: Failed to render login TOTP page: %v\n", err) 352 356 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 440 444 Path: "/", 441 445 }) 442 446 443 - err = logoutTemplate.Execute(w, nil) 447 + err = templates.LogoutTemplate.Execute(w, nil) 444 448 if err != nil { 445 449 fmt.Fprintf(os.Stderr, "WARN: Failed to render logout page: %v\n", err) 446 450 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 460 464 }) 461 465 } 462 466 467 + //go:embed "static" 468 + var staticFS embed.FS 469 + 463 470 func staticHandler() http.Handler { 464 471 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 465 - info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path))) 466 - // does the file exist? 472 + file, err := staticFS.ReadFile(filepath.Join("static", filepath.Clean(r.URL.Path))) 467 473 if err != nil { 468 - if os.IsNotExist(err) { 469 - http.NotFound(w, r) 470 - return 471 - } 472 - } 473 - 474 - // is thjs a directory? (forbidden) 475 - if info.IsDir() { 476 474 http.NotFound(w, r) 477 475 return 478 476 } 479 477 480 - http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r) 478 + w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path))) 479 + w.WriteHeader(http.StatusOK) 480 + 481 + w.Write(file) 481 482 }) 482 483 } 483 484
+8 -7
admin/logshttp.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "arimelody-web/log" 5 - "arimelody-web/model" 6 - "fmt" 7 - "net/http" 8 - "os" 9 - "strings" 4 + "arimelody-web/admin/templates" 5 + "arimelody-web/log" 6 + "arimelody-web/model" 7 + "fmt" 8 + "net/http" 9 + "os" 10 + "strings" 10 11 ) 11 12 12 13 func logsHandler(app *model.AppState) http.Handler { ··· 54 55 Logs []*log.Log 55 56 } 56 57 57 - err = logsTemplate.Execute(w, LogsResponse{ 58 + err = templates.LogsTemplate.Execute(w, LogsResponse{ 58 59 Session: session, 59 60 Logs: logs, 60 61 })
+14 -15
admin/releasehttp.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "fmt" 5 - "net/http" 6 - "strings" 4 + "fmt" 5 + "net/http" 6 + "strings" 7 7 8 - "arimelody-web/controller" 9 - "arimelody-web/model" 8 + "arimelody-web/admin/templates" 9 + "arimelody-web/controller" 10 + "arimelody-web/model" 10 11 ) 11 12 12 13 func serveRelease(app *model.AppState) http.Handler { ··· 60 61 Release *model.Release 61 62 } 62 63 63 - err = releaseTemplate.Execute(w, ReleaseResponse{ 64 + err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{ 64 65 Session: session, 65 66 Release: release, 66 67 }) ··· 74 75 func serveEditCredits(release *model.Release) http.Handler { 75 76 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 76 77 w.Header().Set("Content-Type", "text/html") 77 - err := editCreditsTemplate.Execute(w, release) 78 + err := templates.EditCreditsTemplate.Execute(w, release) 78 79 if err != nil { 79 80 fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err) 80 81 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 97 98 } 98 99 99 100 w.Header().Set("Content-Type", "text/html") 100 - err = addCreditTemplate.Execute(w, response{ 101 + err = templates.AddCreditTemplate.Execute(w, response{ 101 102 ReleaseID: release.ID, 102 103 Artists: artists, 103 104 }) ··· 123 124 } 124 125 125 126 w.Header().Set("Content-Type", "text/html") 126 - err = newCreditTemplate.Execute(w, artist) 127 + err = templates.NewCreditTemplate.Execute(w, artist) 127 128 if err != nil { 128 129 fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err) 129 130 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 134 135 func serveEditLinks(release *model.Release) http.Handler { 135 136 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 136 137 w.Header().Set("Content-Type", "text/html") 137 - err := editLinksTemplate.Execute(w, release) 138 + err := templates.EditCreditsTemplate.Execute(w, release) 138 139 if err != nil { 139 140 fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err) 140 141 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 151 152 Add func(a int, b int) int 152 153 } 153 154 154 - err := editTracksTemplate.Execute(w, editTracksData{ 155 + err := templates.EditTracksTemplate.Execute(w, editTracksData{ 155 156 Release: release, 156 157 Add: func(a, b int) int { return a + b }, 157 158 }) ··· 177 178 } 178 179 179 180 w.Header().Set("Content-Type", "text/html") 180 - err = addTrackTemplate.Execute(w, response{ 181 + err = templates.AddTrackTemplate.Execute(w, response{ 181 182 ReleaseID: release.ID, 182 183 Tracks: tracks, 183 184 }) ··· 185 186 fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err) 186 187 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 187 188 } 188 - return 189 189 }) 190 190 } 191 191 ··· 204 204 } 205 205 206 206 w.Header().Set("Content-Type", "text/html") 207 - err = newTrackTemplate.Execute(w, track) 207 + err = templates.NewTrackTemplate.Execute(w, track) 208 208 if err != nil { 209 209 fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err) 210 210 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 211 211 } 212 - return 213 212 }) 214 213 }
-125
admin/templates.go
··· 1 - package admin 2 - 3 - import ( 4 - "arimelody-web/log" 5 - "fmt" 6 - "html/template" 7 - "path/filepath" 8 - "strings" 9 - "time" 10 - ) 11 - 12 - var indexTemplate = template.Must(template.ParseFiles( 13 - filepath.Join("admin", "views", "layout.html"), 14 - filepath.Join("views", "prideflag.html"), 15 - filepath.Join("admin", "components", "release", "release-list-item.html"), 16 - filepath.Join("admin", "views", "index.html"), 17 - )) 18 - 19 - var loginTemplate = template.Must(template.ParseFiles( 20 - filepath.Join("admin", "views", "layout.html"), 21 - filepath.Join("views", "prideflag.html"), 22 - filepath.Join("admin", "views", "login.html"), 23 - )) 24 - var loginTOTPTemplate = template.Must(template.ParseFiles( 25 - filepath.Join("admin", "views", "layout.html"), 26 - filepath.Join("views", "prideflag.html"), 27 - filepath.Join("admin", "views", "login-totp.html"), 28 - )) 29 - var registerTemplate = template.Must(template.ParseFiles( 30 - filepath.Join("admin", "views", "layout.html"), 31 - filepath.Join("views", "prideflag.html"), 32 - filepath.Join("admin", "views", "register.html"), 33 - )) 34 - var logoutTemplate = template.Must(template.ParseFiles( 35 - filepath.Join("admin", "views", "layout.html"), 36 - filepath.Join("views", "prideflag.html"), 37 - filepath.Join("admin", "views", "logout.html"), 38 - )) 39 - var accountTemplate = template.Must(template.ParseFiles( 40 - filepath.Join("admin", "views", "layout.html"), 41 - filepath.Join("views", "prideflag.html"), 42 - filepath.Join("admin", "views", "edit-account.html"), 43 - )) 44 - var totpSetupTemplate = template.Must(template.ParseFiles( 45 - filepath.Join("admin", "views", "layout.html"), 46 - filepath.Join("views", "prideflag.html"), 47 - filepath.Join("admin", "views", "totp-setup.html"), 48 - )) 49 - var totpConfirmTemplate = template.Must(template.ParseFiles( 50 - filepath.Join("admin", "views", "layout.html"), 51 - filepath.Join("views", "prideflag.html"), 52 - filepath.Join("admin", "views", "totp-confirm.html"), 53 - )) 54 - 55 - var logsTemplate = template.Must(template.New("layout.html").Funcs(template.FuncMap{ 56 - "parseLevel": func(level log.LogLevel) string { 57 - switch level { 58 - case log.LEVEL_INFO: 59 - return "INFO" 60 - case log.LEVEL_WARN: 61 - return "WARN" 62 - } 63 - return fmt.Sprintf("%d?", level) 64 - }, 65 - "titleCase": func(logType string) string { 66 - runes := []rune(logType) 67 - for i, r := range runes { 68 - if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { 69 - runes[i] = r + ('A' - 'a') 70 - } 71 - } 72 - return string(runes) 73 - }, 74 - "lower": func(str string) string { return strings.ToLower(str) }, 75 - "prettyTime": func(t time.Time) string { 76 - // return t.Format("2006-01-02 15:04:05") 77 - // return t.Format("15:04:05, 2 Jan 2006") 78 - return t.Format("02 Jan 2006, 15:04:05") 79 - }, 80 - }).ParseFiles( 81 - filepath.Join("admin", "views", "layout.html"), 82 - filepath.Join("views", "prideflag.html"), 83 - filepath.Join("admin", "views", "logs.html"), 84 - )) 85 - 86 - var releaseTemplate = template.Must(template.ParseFiles( 87 - filepath.Join("admin", "views", "layout.html"), 88 - filepath.Join("views", "prideflag.html"), 89 - filepath.Join("admin", "views", "edit-release.html"), 90 - )) 91 - var artistTemplate = template.Must(template.ParseFiles( 92 - filepath.Join("admin", "views", "layout.html"), 93 - filepath.Join("views", "prideflag.html"), 94 - filepath.Join("admin", "views", "edit-artist.html"), 95 - )) 96 - var trackTemplate = template.Must(template.ParseFiles( 97 - filepath.Join("admin", "views", "layout.html"), 98 - filepath.Join("views", "prideflag.html"), 99 - filepath.Join("admin", "components", "release", "release-list-item.html"), 100 - filepath.Join("admin", "views", "edit-track.html"), 101 - )) 102 - 103 - var editCreditsTemplate = template.Must(template.ParseFiles( 104 - filepath.Join("admin", "components", "credits", "editcredits.html"), 105 - )) 106 - var addCreditTemplate = template.Must(template.ParseFiles( 107 - filepath.Join("admin", "components", "credits", "addcredit.html"), 108 - )) 109 - var newCreditTemplate = template.Must(template.ParseFiles( 110 - filepath.Join("admin", "components", "credits", "newcredit.html"), 111 - )) 112 - 113 - var editLinksTemplate = template.Must(template.ParseFiles( 114 - filepath.Join("admin", "components", "links", "editlinks.html"), 115 - )) 116 - 117 - var editTracksTemplate = template.Must(template.ParseFiles( 118 - filepath.Join("admin", "components", "tracks", "edittracks.html"), 119 - )) 120 - var addTrackTemplate = template.Must(template.ParseFiles( 121 - filepath.Join("admin", "components", "tracks", "addtrack.html"), 122 - )) 123 - var newTrackTemplate = template.Must(template.ParseFiles( 124 - filepath.Join("admin", "components", "tracks", "newtrack.html"), 125 - ))
+128
admin/templates/templates.go
··· 1 + package templates 2 + 3 + import ( 4 + "arimelody-web/log" 5 + "fmt" 6 + "html/template" 7 + "strings" 8 + "time" 9 + _ "embed" 10 + ) 11 + 12 + //go:embed "html/layout.html" 13 + var layoutHTML string 14 + //go:embed "html/prideflag.html" 15 + var prideflagHTML string 16 + 17 + //go:embed "html/index.html" 18 + var indexHTML string 19 + 20 + //go:embed "html/register.html" 21 + var registerHTML string 22 + //go:embed "html/login.html" 23 + var loginHTML string 24 + //go:embed "html/login-totp.html" 25 + var loginTotpHTML string 26 + //go:embed "html/totp-confirm.html" 27 + var totpConfirmHTML string 28 + //go:embed "html/totp-setup.html" 29 + var totpSetupHTML string 30 + //go:embed "html/logout.html" 31 + var logoutHTML string 32 + 33 + //go:embed "html/logs.html" 34 + var logsHTML string 35 + 36 + //go:embed "html/edit-account.html" 37 + var editAccountHTML string 38 + //go:embed "html/edit-artist.html" 39 + var editArtistHTML string 40 + //go:embed "html/edit-release.html" 41 + var editReleaseHTML string 42 + //go:embed "html/edit-track.html" 43 + var editTrackHTML string 44 + 45 + //go:embed "html/components/credits/newcredit.html" 46 + var componentNewCreditHTML string 47 + //go:embed "html/components/credits/addcredit.html" 48 + var componentAddCreditHTML string 49 + //go:embed "html/components/credits/editcredits.html" 50 + var componentEditCreditsHTML string 51 + 52 + //go:embed "html/components/links/editlinks.html" 53 + var componentEditLinksHTML string 54 + 55 + //go:embed "html/components/release/release-list-item.html" 56 + var componentReleaseListItemHTML string 57 + 58 + //go:embed "html/components/tracks/newtrack.html" 59 + var componentNewTrackHTML string 60 + //go:embed "html/components/tracks/addtrack.html" 61 + var componentAddTrackHTML string 62 + //go:embed "html/components/tracks/edittracks.html" 63 + var componentEditTracksHTML string 64 + 65 + var BaseTemplate = template.Must(template.New("base").Parse( 66 + strings.Join([]string{ layoutHTML, prideflagHTML }, "\n"), 67 + )) 68 + 69 + var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 70 + strings.Join([]string{ 71 + indexHTML, 72 + componentReleaseListItemHTML, 73 + }, "\n"), 74 + )) 75 + 76 + var LoginTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginHTML)) 77 + var LoginTOTPTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginTotpHTML)) 78 + var RegisterTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(registerHTML)) 79 + var LogoutTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(logoutHTML)) 80 + var AccountTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editAccountHTML)) 81 + var TOTPSetupTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpSetupHTML)) 82 + var TOTPConfirmTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpConfirmHTML)) 83 + 84 + var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(template.FuncMap{ 85 + "parseLevel": func(level log.LogLevel) string { 86 + switch level { 87 + case log.LEVEL_INFO: 88 + return "INFO" 89 + case log.LEVEL_WARN: 90 + return "WARN" 91 + } 92 + return fmt.Sprintf("%d?", level) 93 + }, 94 + "titleCase": func(logType string) string { 95 + runes := []rune(logType) 96 + for i, r := range runes { 97 + if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { 98 + runes[i] = r + ('A' - 'a') 99 + } 100 + } 101 + return string(runes) 102 + }, 103 + "lower": func(str string) string { return strings.ToLower(str) }, 104 + "prettyTime": func(t time.Time) string { 105 + // return t.Format("2006-01-02 15:04:05") 106 + // return t.Format("15:04:05, 2 Jan 2006") 107 + return t.Format("02 Jan 2006, 15:04:05") 108 + }, 109 + }).Parse(logsHTML)) 110 + 111 + var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML)) 112 + var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML)) 113 + var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 114 + strings.Join([]string{ 115 + editTrackHTML, 116 + componentReleaseListItemHTML, 117 + }, "\n"), 118 + )) 119 + 120 + var EditCreditsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditCreditsHTML)) 121 + var AddCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddCreditHTML)) 122 + var NewCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewCreditHTML)) 123 + 124 + var EditLinksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditLinksHTML)) 125 + 126 + var EditTracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditTracksHTML)) 127 + var AddTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddTrackHTML)) 128 + var NewTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewTrackHTML))
+7 -6
admin/trackhttp.go
··· 1 1 package admin 2 2 3 3 import ( 4 - "fmt" 5 - "net/http" 6 - "strings" 4 + "fmt" 5 + "net/http" 6 + "strings" 7 7 8 - "arimelody-web/model" 9 - "arimelody-web/controller" 8 + "arimelody-web/admin/templates" 9 + "arimelody-web/controller" 10 + "arimelody-web/model" 10 11 ) 11 12 12 13 func serveTrack(app *model.AppState) http.Handler { ··· 39 40 40 41 session := r.Context().Value("session").(*model.Session) 41 42 42 - err = trackTemplate.Execute(w, TrackResponse{ 43 + err = templates.EditTrackTemplate.Execute(w, TrackResponse{ 43 44 Session: session, 44 45 Track: track, 45 46 Releases: releases,
admin/views/edit-account.html admin/templates/html/edit-account.html
admin/views/edit-artist.html admin/templates/html/edit-artist.html
admin/views/edit-release.html admin/templates/html/edit-release.html
admin/views/edit-track.html admin/templates/html/edit-track.html
admin/views/index.html admin/templates/html/index.html
admin/views/layout.html admin/templates/html/layout.html
admin/views/login-totp.html admin/templates/html/login-totp.html
admin/views/login.html admin/templates/html/login.html
admin/views/logout.html admin/templates/html/logout.html
admin/views/logs.html admin/templates/html/logs.html
admin/views/register.html admin/templates/html/register.html
admin/views/totp-confirm.html admin/templates/html/totp-confirm.html
admin/views/totp-setup.html admin/templates/html/totp-setup.html
+30 -25
main.go
··· 1 1 package main 2 2 3 3 import ( 4 - "bufio" 5 - "errors" 6 - "fmt" 7 - stdLog "log" 8 - "math" 9 - "math/rand" 10 - "net" 11 - "net/http" 12 - "os" 13 - "path/filepath" 14 - "strconv" 15 - "strings" 16 - "time" 4 + "bufio" 5 + "embed" 6 + "errors" 7 + "fmt" 8 + stdLog "log" 9 + "math" 10 + "math/rand" 11 + "net" 12 + "net/http" 13 + "os" 14 + "path/filepath" 15 + "strconv" 16 + "strings" 17 + "time" 17 18 18 - "arimelody-web/admin" 19 - "arimelody-web/api" 20 - "arimelody-web/colour" 21 - "arimelody-web/controller" 22 - "arimelody-web/cursor" 23 - "arimelody-web/log" 24 - "arimelody-web/model" 25 - "arimelody-web/view" 19 + "arimelody-web/admin" 20 + "arimelody-web/api" 21 + "arimelody-web/colour" 22 + "arimelody-web/controller" 23 + "arimelody-web/cursor" 24 + "arimelody-web/log" 25 + "arimelody-web/model" 26 + "arimelody-web/view" 26 27 27 - "github.com/jmoiron/sqlx" 28 - _ "github.com/lib/pq" 29 - "golang.org/x/crypto/bcrypt" 28 + "github.com/jmoiron/sqlx" 29 + _ "github.com/lib/pq" 30 + "golang.org/x/crypto/bcrypt" 30 31 ) 31 32 32 33 // used for database migrations ··· 35 36 const DEFAULT_PORT int64 = 8080 36 37 const HRT_DATE int64 = 1756478697 37 38 39 + //go:embed "public" 40 + var publicFS embed.FS 41 + 38 42 func main() { 39 43 fmt.Printf("made with <3 by ari melody\n\n") 40 44 41 45 app := model.AppState{ 42 46 Config: controller.GetConfig(), 43 47 Twitch: nil, 48 + PublicFS: publicFS, 44 49 } 45 50 46 51 // initialise database connection ··· 526 531 mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app))) 527 532 mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app))) 528 533 mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app))) 529 - mux.Handle("/uploads/", http.StripPrefix("/uploads", view.StaticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) 534 + mux.Handle("/uploads/", http.StripPrefix("/uploads", view.ServeFiles(filepath.Join(app.Config.DataDirectory, "uploads")))) 530 535 mux.Handle("/cursor-ws", cursor.Handler(app)) 531 536 mux.Handle("/", view.IndexHandler(app)) 532 537
+5 -2
model/appstate.go
··· 1 1 package model 2 2 3 3 import ( 4 - "github.com/jmoiron/sqlx" 4 + "embed" 5 5 6 - "arimelody-web/log" 6 + "github.com/jmoiron/sqlx" 7 + 8 + "arimelody-web/log" 7 9 ) 8 10 9 11 type ( ··· 43 45 Config Config 44 46 Log log.Logger 45 47 Twitch *TwitchState 48 + PublicFS embed.FS 46 49 } 47 50 )
+21
templates/html/prideflag.html
··· 1 + {{define "prideflag"}} 2 + <a href="https://github.com/arimelody/prideflag" target="_blank" id="prideflag"> 3 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" hx-preserve="true"> 4 + <path id="red" d="M120,80 L100,100 L120,120 Z" style="fill:#d20605"/> 5 + <path id="orange" d="M120,80 V40 L80,80 L100,100 Z" style="fill:#ef9c00"/> 6 + <path id="yellow" d="M120,40 V0 L60,60 L80,80 Z" style="fill:#e5fe02"/> 7 + <path id="green" d="M120,0 H80 L40,40 L60,60 Z" style="fill:#09be01"/> 8 + <path id="blue" d="M80,0 H40 L20,20 L40,40 Z" style="fill:#081a9a"/> 9 + <path id="purple" d="M40,0 H0 L20,20 Z" style="fill:#76008a"/> 10 + 11 + <rect id="black" x="60" width="60" height="60" style="fill:#010101"/> 12 + <rect id="brown" x="70" width="50" height="50" style="fill:#603814"/> 13 + <rect id="lightblue" x="80" width="40" height="40" style="fill:#73d6ed"/> 14 + <rect id="pink" x="90" width="30" height="30" style="fill:#ffafc8"/> 15 + <rect id="white" x="100" width="20" height="20" style="fill:#fff"/> 16 + 17 + <rect id="intyellow" x="110" width="10" height="10" style="fill:#fed800"/> 18 + <circle id="intpurple" cx="120" cy="0" r="5" stroke="#7601ad" stroke-width="2" fill="none"/> 19 + </svg> 20 + </a> 21 + {{end}}
+30 -22
templates/templates.go
··· 1 1 package templates 2 2 3 3 import ( 4 - "html/template" 5 - "path/filepath" 4 + _ "embed" 5 + "html/template" 6 + "strings" 6 7 ) 7 8 8 - var IndexTemplate = template.Must(template.ParseFiles( 9 - filepath.Join("views", "layout.html"), 10 - filepath.Join("views", "header.html"), 11 - filepath.Join("views", "footer.html"), 12 - filepath.Join("views", "prideflag.html"), 13 - filepath.Join("views", "index.html"), 14 - )) 15 - var MusicTemplate = template.Must(template.ParseFiles( 16 - filepath.Join("views", "layout.html"), 17 - filepath.Join("views", "header.html"), 18 - filepath.Join("views", "footer.html"), 19 - filepath.Join("views", "prideflag.html"), 20 - filepath.Join("views", "music.html"), 21 - )) 22 - var MusicGatewayTemplate = template.Must(template.ParseFiles( 23 - filepath.Join("views", "layout.html"), 24 - filepath.Join("views", "header.html"), 25 - filepath.Join("views", "footer.html"), 26 - filepath.Join("views", "prideflag.html"), 27 - filepath.Join("views", "music-gateway.html"), 9 + //go:embed "html/layout.html" 10 + var layoutHTML string 11 + //go:embed "html/header.html" 12 + var headerHTML string 13 + //go:embed "html/footer.html" 14 + var footerHTML string 15 + //go:embed "html/prideflag.html" 16 + var prideflagHTML string 17 + //go:embed "html/index.html" 18 + var indexHTML string 19 + //go:embed "html/music.html" 20 + var musicHTML string 21 + //go:embed "html/music-gateway.html" 22 + var musicGatewayHTML string 23 + // //go:embed "html/404.html" 24 + // var error404HTML string 25 + 26 + var BaseTemplate = template.Must(template.New("base").Parse( 27 + strings.Join([]string{ 28 + layoutHTML, 29 + headerHTML, 30 + footerHTML, 31 + prideflagHTML, 32 + }, "\n"), 28 33 )) 34 + var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(indexHTML)) 35 + var MusicTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicHTML)) 36 + var MusicGatewayTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(musicGatewayHTML))
+48
view/files.go
··· 1 + package view 2 + 3 + import ( 4 + "embed" 5 + "errors" 6 + "mime" 7 + "net/http" 8 + "os" 9 + "path" 10 + "path/filepath" 11 + ) 12 + 13 + func ServeEmbedFS(fs embed.FS, dir string) http.Handler { 14 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 + file, err := fs.ReadFile(filepath.Join(dir, filepath.Clean(r.URL.Path))) 16 + if err != nil { 17 + http.NotFound(w, r) 18 + return 19 + } 20 + 21 + w.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(r.URL.Path))) 22 + w.WriteHeader(http.StatusOK) 23 + 24 + w.Write(file) 25 + }) 26 + } 27 + 28 + func ServeFiles(directory string) http.Handler { 29 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 + info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path))) 31 + 32 + // does the file exist? 33 + if err != nil { 34 + if errors.Is(err, os.ErrNotExist) { 35 + http.NotFound(w, r) 36 + return 37 + } 38 + } 39 + 40 + // is thjs a directory? (forbidden) 41 + if info.IsDir() { 42 + http.NotFound(w, r) 43 + return 44 + } 45 + 46 + http.FileServer(http.Dir(directory)).ServeHTTP(w, r) 47 + }) 48 + }
+1 -1
view/index.go
··· 40 40 return 41 41 } 42 42 43 - StaticHandler("public").ServeHTTP(w, r) 43 + ServeEmbedFS(app.PublicFS, "public").ServeHTTP(w, r) 44 44 }) 45 45 }
-31
view/static.go
··· 1 - package view 2 - 3 - import ( 4 - "errors" 5 - "net/http" 6 - "os" 7 - "path/filepath" 8 - ) 9 - 10 - func StaticHandler(directory string) http.Handler { 11 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 - info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path))) 13 - 14 - // does the file exist? 15 - if err != nil { 16 - if errors.Is(err, os.ErrNotExist) { 17 - http.NotFound(w, r) 18 - return 19 - } 20 - } 21 - 22 - // is thjs a directory? (forbidden) 23 - if info.IsDir() { 24 - http.NotFound(w, r) 25 - return 26 - } 27 - 28 - http.FileServer(http.Dir(directory)).ServeHTTP(w, r) 29 - }) 30 - } 31 -
views/404.html templates/html/404.html
views/footer.html templates/html/footer.html
views/header.html templates/html/header.html
views/index.html templates/html/index.html
views/layout.html templates/html/layout.html
views/music-gateway.html templates/html/music-gateway.html
views/music.html templates/html/music.html
views/prideflag.html admin/templates/html/prideflag.html