embed html template and static files

This commit is contained in:
ari melody 2025-09-30 19:03:35 +01:00
parent b150fa491c
commit e5dcc4b884
Signed by: ari
GPG key ID: CF99829C92678188
44 changed files with 316 additions and 255 deletions

View file

@ -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:

View file

@ -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),

View file

@ -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,

View file

@ -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)
})
}

View file

@ -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,
})

View file

@ -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
})
}

View file

@ -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"),
))

View file

@ -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))

View file

@ -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,

55
main.go
View file

@ -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))

View file

@ -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
}
)

View file

@ -0,0 +1,21 @@
{{define "prideflag"}}
<a href="https://github.com/arimelody/prideflag" target="_blank" id="prideflag">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="120" height="120" hx-preserve="true">
<path id="red" d="M120,80 L100,100 L120,120 Z" style="fill:#d20605"/>
<path id="orange" d="M120,80 V40 L80,80 L100,100 Z" style="fill:#ef9c00"/>
<path id="yellow" d="M120,40 V0 L60,60 L80,80 Z" style="fill:#e5fe02"/>
<path id="green" d="M120,0 H80 L40,40 L60,60 Z" style="fill:#09be01"/>
<path id="blue" d="M80,0 H40 L20,20 L40,40 Z" style="fill:#081a9a"/>
<path id="purple" d="M40,0 H0 L20,20 Z" style="fill:#76008a"/>
<rect id="black" x="60" width="60" height="60" style="fill:#010101"/>
<rect id="brown" x="70" width="50" height="50" style="fill:#603814"/>
<rect id="lightblue" x="80" width="40" height="40" style="fill:#73d6ed"/>
<rect id="pink" x="90" width="30" height="30" style="fill:#ffafc8"/>
<rect id="white" x="100" width="20" height="20" style="fill:#fff"/>
<rect id="intyellow" x="110" width="10" height="10" style="fill:#fed800"/>
<circle id="intpurple" cx="120" cy="0" r="5" stroke="#7601ad" stroke-width="2" fill="none"/>
</svg>
</a>
{{end}}

View file

@ -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))

View file

@ -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)
})
}

View file

@ -40,6 +40,6 @@ func IndexHandler(app *model.AppState) http.Handler {
return
}
StaticHandler("public").ServeHTTP(w, r)
ServeEmbedFS(app.PublicFS, "public").ServeHTTP(w, r)
})
}