From bcf66d92d664dd707937ae866830a6bee0751745 Mon Sep 17 00:00:00 2001 From: Tim Keller Date: Sat, 24 May 2025 12:52:32 -0500 Subject: cleanup all go code and add an index/home page that is composed from the readme file --- Dockerfile | 1 + api.go | 58 +++++++++++++++++++++++++++++++++ docker-compose.yaml | 2 +- go.mod | 5 ++- go.sum | 2 ++ home.go | 43 +++++++++++++++++++++++++ main.go | 90 +++++++++++----------------------------------------- templates/base.html | 12 +++++++ templates/index.html | 3 ++ templates/watch.html | 89 ++++++++++++++++++++++++--------------------------- util.go | 25 +++++++++++++++ watch.go | 34 ++++++++++++++++++++ 12 files changed, 243 insertions(+), 121 deletions(-) create mode 100644 api.go create mode 100644 home.go create mode 100644 templates/base.html create mode 100644 templates/index.html create mode 100644 util.go create mode 100644 watch.go diff --git a/Dockerfile b/Dockerfile index 7e389c4..ce83df8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ FROM alpine:latest WORKDIR /app COPY --from=builder /app/mintube . +COPY --from=builder /app/README.md . COPY --from=builder /app/templates ./templates COPY --from=builder /app/static ./static diff --git a/api.go b/api.go new file mode 100644 index 0000000..fa91ea6 --- /dev/null +++ b/api.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" +) + +const youtubeAPI = "https://www.googleapis.com/youtube/v3/" + +/* function to make an api request */ +func apiRequest(w http.ResponseWriter, r *http.Request, endpoint string, videoIdParam string, part string) { + videoID := r.URL.Query().Get("id") + if videoID == "" { + msg := "Missing ?id=VIDEO_ID parameter" + http.Error(w, msg, http.StatusBadRequest) + return + } + + if apiKey == "" { + msg := "API_KEY environment variable not set" + http.Error(w, msg, http.StatusInternalServerError) + log.Println(msg) + return + } + + url := fmt.Sprintf("%s?part=%s&%s=%s&key=%s", youtubeAPI + endpoint, part, videoIdParam, videoID, apiKey) + resp, err := http.Get(url) + if err != nil { + msg := "Failed to fetch video info: " + err.Error() + http.Error(w, msg, http.StatusInternalServerError) + log.Println(msg) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + msg := "YouTube API error: " + resp.Status + http.Error(w, msg, http.StatusBadGateway) + log.Println(msg) + return + } + + w.Header().Set("Content-Type", "application/json") + io.Copy(w, resp.Body) +} + +/* routes */ +func setupRoutesAPI() { + http.HandleFunc("/api/details", func(w http.ResponseWriter, r *http.Request) { + apiRequest(w, r, "videos", "id", "snippet,statistics,topicDetails") + }) + + http.HandleFunc("/api/comments", func(w http.ResponseWriter, r *http.Request) { + apiRequest(w, r, "commentThreads", "videoId", "snippet,replies&maxResults=100") + }) +} diff --git a/docker-compose.yaml b/docker-compose.yaml index bd98f85..3ad6b28 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,4 +6,4 @@ services: - 7008:8080 # TOOB # Add api key here, or in .env file #environment: - # - API_KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + # API_KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/go.mod b/go.mod index 92f4caa..c2c4797 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module tjkeller.xyz/mintube go 1.23.7 -require github.com/joho/godotenv v1.5.1 +require ( + github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b + github.com/joho/godotenv v1.5.1 +) diff --git a/go.sum b/go.sum index d61b19e..8dc4786 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/home.go b/home.go new file mode 100644 index 0000000..48feaef --- /dev/null +++ b/home.go @@ -0,0 +1,43 @@ +package main + +import ( + "html/template" + "io/ioutil" + "log" + "net/http" + "github.com/gomarkdown/markdown" +) + +var templateIndexFiles = []string{ "templates/base.html", "templates/index.html" } +var templateIndex = template.Must(template.ParseFiles(templateIndexFiles...)) +var contentHTML template.HTML // just display README.md + +type IndexTemplateData struct { + Content template.HTML +} + +func loadReadme() { + mdBytes, err := ioutil.ReadFile("README.md") + if err != nil { + log.Fatalf("Failed to read markdown file: %v", err) + } + htmlBytes := markdown.ToHTML(mdBytes, nil, nil) + contentHTML = template.HTML(htmlBytes) +} + +func renderIndexTemplate(w http.ResponseWriter) { + if debug { + reloadTemplate(&templateIndex, templateIndexFiles...) + loadReadme() + } + err := templateIndex.Execute(w, IndexTemplateData{ + Content: contentHTML, + }) + if err != nil { + templateError(err, w) + } +} + +func setupHome() { + loadReadme() +} diff --git a/main.go b/main.go index 511d7d6..e85649d 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,6 @@ package main import ( - "fmt" - "html/template" - "io" "log" "net/http" "os" @@ -11,86 +8,35 @@ import ( "github.com/joho/godotenv" ) -const youtubeAPI = "https://www.googleapis.com/youtube/v3/" -var tmpl = template.Must(template.ParseFiles("templates/watch.html")) +var debug = false +var apiKey = "" -func apiRequest(w http.ResponseWriter, r *http.Request, endpoint string, videoIdParam string, part string) { - videoID := r.URL.Query().Get("id") - if videoID == "" { - msg := "Missing ?id=VIDEO_ID parameter" - http.Error(w, msg, http.StatusBadRequest) - return - } - - apiKey := os.Getenv("API_KEY") - if apiKey == "" { - msg := "API_KEY environment variable not set" - http.Error(w, msg, http.StatusInternalServerError) - log.Println(msg) - return - } - - //url := fmt.Sprintf("%s?part=snippet&id=%s&key=%s", youtubeAPI, videoID, apiKey) - url := fmt.Sprintf("%s?part=%s&%s=%s&key=%s", youtubeAPI + endpoint, part, videoIdParam, videoID, apiKey) - resp, err := http.Get(url) - if err != nil { - msg := "Failed to fetch video info: " + err.Error() - http.Error(w, msg, http.StatusInternalServerError) - log.Println(msg) - return - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - msg := "YouTube API error: " + resp.Status - http.Error(w, msg, http.StatusBadGateway) - log.Println(msg) - return - } - - w.Header().Set("Content-Type", "application/json") - io.Copy(w, resp.Body) -} - -func videoDetailsHandler(w http.ResponseWriter, r *http.Request) { - apiRequest(w, r, "videos", "id", "snippet,statistics,topicDetails") -} - -func commentThreadsHandler(w http.ResponseWriter, r *http.Request) { - apiRequest(w, r, "commentThreads", "videoId", "snippet,replies&maxResults=100") -} - -func handler(w http.ResponseWriter, r *http.Request) { - id := strings.Trim(r.URL.Path, "/") - //if path == "" { - // TODO landing page - //} - if id == "watch" { - id = r.URL.Query().Get("v") - } - data := struct { - Id string - }{ - Id: id, - } - err := tmpl.Execute(w, data) - if err != nil { - msg := "Template execution error" - http.Error(w, msg, http.StatusInternalServerError) - log.Println(msg) +/* root handler */ +func rootHandler(w http.ResponseWriter, r *http.Request) { + path := strings.Trim(r.URL.Path, "/") + if path == "" { + //http.ServeFile(w, r, "static/index.html") + renderIndexTemplate(w) + } else { + renderWatchTemplate(w, path) } } +/* main */ func main() { // load .env file if it exists godotenv.Load() + debug = os.Getenv("DEBUG") != "" + apiKey = os.Getenv("API_KEY") // setup routes + setupRoutesAPI() + setupRoutesWatch() + setupHome() http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - http.HandleFunc("/api/details", videoDetailsHandler) - http.HandleFunc("/api/comments", commentThreadsHandler) + http.HandleFunc("/", rootHandler) - http.HandleFunc("/", handler) + // start http server log.Println("Listening on http://localhost:8080") err := http.ListenAndServe(":8080", nil) if err != nil { diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..7c1f8eb --- /dev/null +++ b/templates/base.html @@ -0,0 +1,12 @@ + + + + + + + {{ block "scripts" . }}{{ end }} + + + {{ block "content" . }}{{ end }} + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c4ade88 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,3 @@ +{{ define "content" }} + {{ .Content }} +{{ end }} diff --git a/templates/watch.html b/templates/watch.html index bae485d..be4be41 100644 --- a/templates/watch.html +++ b/templates/watch.html @@ -1,48 +1,43 @@ - - - - - - - - - - - -

- - +{{ define "scripts" }} + + +{{ end }} + +{{ define "content" }} + +

+ + +
+ +{{ end }} diff --git a/util.go b/util.go new file mode 100644 index 0000000..6381bff --- /dev/null +++ b/util.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" +) + +func reloadTemplate(t **template.Template, files ...string) error { + tmpl, err := template.ParseFiles(files...) + if err != nil { + msg := fmt.Sprintf("Template load error: %v", err) + log.Println(msg) + } else { + *t = tmpl + } + return err +} + +func templateError(err error, w http.ResponseWriter) { + msg := "Template execution error" + http.Error(w, msg, http.StatusInternalServerError) + log.Println(msg) +} diff --git a/watch.go b/watch.go new file mode 100644 index 0000000..8352e62 --- /dev/null +++ b/watch.go @@ -0,0 +1,34 @@ +package main + +import ( + "html/template" + "net/http" +) + +var templateWatchFiles = []string{ "templates/base.html", "templates/watch.html" } +var templateWatch = template.Must(template.ParseFiles(templateWatchFiles...)) + +/* render template */ +type WatchTemplateData struct { + Id string +} + +func renderWatchTemplate(w http.ResponseWriter, id string) { + if debug { + reloadTemplate(&templateWatch, templateWatchFiles...) + } + err := templateWatch.Execute(w, WatchTemplateData{ + Id: id, + }) + if err != nil { + templateError(err, w) + } +} + +/* routes */ +func setupRoutesWatch() { + http.HandleFunc("/watch", func(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("v") + renderWatchTemplate(w, id) + }) +} -- cgit v1.2.3