From 8de2c0bd743b2a2e298aa619d8d05d3687b7d799 Mon Sep 17 00:00:00 2001 From: Martyn Ranyard Date: Sat, 1 Aug 2020 11:37:40 +0200 Subject: [PATCH 1/2] Serve the React frontend Signed-off-by: Martyn Ranyard --- .gitignore | 3 ++ internal/webserver/webserver.go | 69 ++++++++++++++++++++++++++++++--- web/data.json | 10 +++++ web/topsingers.json | 4 ++ web/topsongs.json | 4 ++ 5 files changed, 84 insertions(+), 6 deletions(-) create mode 100755 web/data.json create mode 100755 web/topsingers.json create mode 100755 web/topsongs.json diff --git a/.gitignore b/.gitignore index bcdff6f..7497f1c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +# Frontend compiled version gets built from source +/web/react-frontend + /twitchsingstools diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index ecb4e06..d1e240f 100755 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -778,23 +778,80 @@ func CSVHandler(response http.ResponseWriter, request *http.Request) { } } +func JSONHandler(response http.ResponseWriter, request *http.Request) { + vars := mux.Vars(request) + if vars["key"] != ircBot.ChannelData[vars["channel"]].AdminKey { + UnauthorizedHandler(response, request) + return + } + type TemplateData struct { + Channel string + Command string + ExtraStrings string + SinceTime time.Time + SinceTimeUTC string + Leaving bool + HasLeft bool + SongData []AugmentedSingsVideoStruct + TopNSongs []SongSings + TopNSingers []SingerSings + } + channelData := ircBot.ChannelData[vars["channel"]] + if time.Now().Sub(ircBot.ChannelData[vars["channel"]].VideoCacheUpdated).Hours() > 1 { + fmt.Printf("Cache of %d performances is older than an hour - %.1f hours old to be precise... fetching.\n", len(ircBot.ChannelData[vars["channel"]].VideoCache), time.Now().Sub(ircBot.ChannelData[vars["channel"]].VideoCacheUpdated).Hours()) + vids, err := fetchAllVoDs(channelData.TwitchUserID, channelData.Bearer) + if err != nil { + errCache := make([]irc.SingsVideoStruct, 0) + var ret irc.SingsVideoStruct + ret.FullTitle = "Error fetching videos: " + err.Error() + errCache = append(errCache, ret) + vids = errCache + } + updateCalculatedFields(vids) + ircBot.UpdateVideoCache(vars["channel"], vids) + } else { + fmt.Printf("Cache of %d performances is younger than an hour - %.1f hours old to be precise... not fetching.\n", len(ircBot.ChannelData[vars["channel"]].VideoCache), time.Now().Sub(ircBot.ChannelData[vars["channel"]].VideoCacheUpdated).Hours()) + } + topNSongs := calculateTopNSongs(channelData.VideoCache, 10) + topNSingers := calculateTopNSingers(channelData.VideoCache, 10) + var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, channelData.JoinTime.Format(irc.UTCFormat), false, channelData.HasLeft, AugmentSingsVideoStructSlice(channelData.VideoCache), topNSongs, topNSingers} + response.Header().Add("Content-type", "application/json") + if request.URL.Path[0:5] == "/json" { + tmpl := template.Must(template.ParseFiles("web/data.json")) + tmpl.Execute(response, td) + } else if request.URL.Path[0:9] == "/topsongs" { + tmpl := template.Must(template.ParseFiles("web/topsongs.json")) + tmpl.Execute(response, td) + } else { // top 10 singers! + tmpl := template.Must(template.ParseFiles("web/topsingers.json")) + tmpl.Execute(response, td) + } +} + +func ReactIndexHandler(entrypoint string) func(w http.ResponseWriter, r *http.Request) { + fn := func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, entrypoint) + } + + return http.HandlerFunc(fn) +} + func HandleHTTP(passedIrcBot *irc.KardBot) { ircBot = passedIrcBot r := mux.NewRouter() loggedRouter := handlers.LoggingHandler(os.Stdout, r) r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) - r.HandleFunc("/", RootHandler) r.HandleFunc("/healthz", HealthHandler) r.HandleFunc("/web/{.*}", TemplateHandler) - r.PathPrefix("/static/").Handler(http.FileServer(http.Dir("./web/"))) - r.HandleFunc("/cover.css", CSSHandler) - r.HandleFunc("/admin/{channel}/{key}", AdminHandler) r.HandleFunc("/csv/{channel}/{key}", CSVHandler) r.HandleFunc("/tsv/{channel}/{key}", CSVHandler) - //r.HandleFunc("/twitchadmin", TwitchAdminHandler) - //r.HandleFunc("/twitchtobackend", TwitchBackendHandler) + r.HandleFunc("/json/{channel}/{key}", JSONHandler) + r.HandleFunc("/topsongs/{channel}/{key}", JSONHandler) + r.HandleFunc("/topsingers/{channel}/{key}", JSONHandler) r.Path("/twitchtobackend").Queries("access_token", "{access_token}", "scope", "{scope}", "token_type", "{token_type}").HandlerFunc(TwitchBackendHandler) r.Path("/twitchadmin").Queries("code", "{code}", "scope", "{scope}").HandlerFunc(TwitchAdminHandler) + r.PathPrefix("/static").Handler(http.StripPrefix("/static", http.FileServer(http.Dir("./web/react-frontend/static")))) + r.PathPrefix("/").HandlerFunc(ReactIndexHandler("./web/react-frontend/index.html")) http.Handle("/", r) srv := &http.Server{ Handler: loggedRouter, diff --git a/web/data.json b/web/data.json new file mode 100755 index 0000000..a1f16be --- /dev/null +++ b/web/data.json @@ -0,0 +1,10 @@ +[{{ range $index, $data := .SongData }}{{ if $index }},{{end}}{ + "publishDate": "{{ $data.Date }}", + "displayPublishDate": "{{ $data.NiceDate }}", + "songName": "{{ $data.SongTitle }}", + "singerName": "{{ $data.OtherSinger }}", + "lastSongDate": "{{ $data.LastSungSong }}", + "displayLastSongDate": "{{ $data.NiceLastSungSong }}", + "lastDuetDate": "{{ $data.LastSungSinger }}", + "displayLastDuetDate": "{{ $data.NiceLastSungSinger }}" +}{{ end }}] diff --git a/web/topsingers.json b/web/topsingers.json new file mode 100755 index 0000000..0f1e1f5 --- /dev/null +++ b/web/topsingers.json @@ -0,0 +1,4 @@ +[{{ range $index, $data := .TopNSingers }}{{ if $index }},{{end}}{ + "singerName": "{{ .SingerName }}", + "singCount": "{{ .Sings }}" +}{{ end }}] diff --git a/web/topsongs.json b/web/topsongs.json new file mode 100755 index 0000000..16315d1 --- /dev/null +++ b/web/topsongs.json @@ -0,0 +1,4 @@ +[{{ range $index, $data := .TopNSongs }}{{ if $index }},{{end}}{ + "songName": "{{ .SongTitle }}", + "singCount": "{{ .Sings }}" +}{{ end }}] From 4c27138497b8f9aadca4531c676e962b1a32d1f1 Mon Sep 17 00:00:00 2001 From: Martyn Ranyard Date: Sat, 1 Aug 2020 11:38:05 +0200 Subject: [PATCH 2/2] Build the React frontend Signed-off-by: Martyn Ranyard --- Makefile | 5 +++++ build/ci/drone.yml | 5 +++++ build/package/Dockerfile | 7 ++++++- build/react-frontend | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) create mode 160000 build/react-frontend diff --git a/Makefile b/Makefile index c1d7692..1d9e877 100755 --- a/Makefile +++ b/Makefile @@ -10,6 +10,11 @@ test: build: go build ${LDFLAGS} +build-frontend: + cd build/react-frontend && npm run build + rm -rf web/react-frontend ; mkdir -p web/react-frontend + cp -r build/react-frontend/build/* web/react-frontend/ + deps: go get diff --git a/build/ci/drone.yml b/build/ci/drone.yml index 3df9267..1735fa4 100644 --- a/build/ci/drone.yml +++ b/build/ci/drone.yml @@ -59,6 +59,11 @@ steps: - make test - make +- name: build-frontend + image: node + commands: + - make frontend + trigger: ref: - refs/heads/main diff --git a/build/package/Dockerfile b/build/package/Dockerfile index 7fa9cba..466f374 100755 --- a/build/package/Dockerfile +++ b/build/package/Dockerfile @@ -5,9 +5,14 @@ COPY internal/ /go/src/git.martyn.berlin/martyn/twitchsingstools/internal/ COPY Makefile /go/src/git.martyn.berlin/martyn/twitchsingstools/ RUN cd /go/src/git.martyn.berlin/martyn/twitchsingstools/; make deps ; make static -FROM scratch +FROM library/node:14.7.0-stretch AS frontend +COPY build/react-frontend /frontend +RUN cd /frontend; npm run build + +FROM debian COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /go/src/git.martyn.berlin/martyn/twitchsingstools /app/ COPY web/ /app/web/ +COPY --from=frontend /frontend/build /app/web/react-frontend WORKDIR /app CMD ["/app/twitchsingstools"] diff --git a/build/react-frontend b/build/react-frontend new file mode 160000 index 0000000..5d2ed3c --- /dev/null +++ b/build/react-frontend @@ -0,0 +1 @@ +Subproject commit 5d2ed3c5be7d050e48b6bedbd2591543d732bf52