aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Keller <tjkeller.xyz>2025-05-24 12:52:32 -0500
committerTim Keller <tjkeller.xyz>2025-05-24 12:52:32 -0500
commitbcf66d92d664dd707937ae866830a6bee0751745 (patch)
tree5c1f7ecc037b53b434befe71509cc3009beaf3d7
parent6b0385c495b246859d27bfa75e1bd4dfa45c2be2 (diff)
downloadmintube-master.tar.xz
mintube-master.zip
cleanup all go code and add an index/home page that is composed from the readme fileHEADmaster
-rw-r--r--Dockerfile1
-rw-r--r--api.go58
-rw-r--r--docker-compose.yaml2
-rw-r--r--go.mod5
-rw-r--r--go.sum2
-rw-r--r--home.go43
-rw-r--r--main.go90
-rw-r--r--templates/base.html12
-rw-r--r--templates/index.html3
-rw-r--r--templates/watch.html89
-rw-r--r--util.go25
-rw-r--r--watch.go34
12 files changed, 243 insertions, 121 deletions
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 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
+ <link rel="stylesheet" href="/static/style.css">
+ {{ block "scripts" . }}{{ end }}
+</head>
+<body>
+ {{ block "content" . }}{{ end }}
+</body>
+</html>
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 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
- <link rel="stylesheet" href="/static/style.css">
- <script src="/static/index.js" defer></script>
- <script src="https://www.youtube.com/iframe_api"></script>
-</head>
-<body>
- <iframe
- id="player"
- src="https://www.youtube-nocookie.com/embed/{{ .Id }}?enablejsapi=1&autoplay=1"
- frameborder="0"
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
- referrerpolicy="no-referrer"
- allowfullscreen
- >
- </iframe>
- <h1 id="title"></h1>
- <button id="toggle-details">Show Details</button>
- <div id="details" style="display:none">
- <span><a id="details-channel"></a> &ensp; 👍 <span id="details-likes"></span></span>
- <br>
- <span><span id="details-views"></span> Views &ensp; <span id="details-date"></span></span>
- <br>
- <h3>Description:</h3>
- <span id="details-desc">No description has been added to this video.</span>
- <br>
- <h4>Tags:</h4>
- <div id="details-tags"></div>
- </div>
+{{ define "scripts" }}
+<script src="/static/index.js" defer></script>
+<script src="https://www.youtube.com/iframe_api"></script>
+{{ end }}
+
+{{ define "content" }}
+<iframe
+ id="player"
+ src="https://www.youtube-nocookie.com/embed/{{ .Id }}?enablejsapi=1&autoplay=1"
+ frameborder="0"
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+ referrerpolicy="no-referrer"
+ allowfullscreen
+>
+</iframe>
+<h1 id="title"></h1>
+<button id="toggle-details">Show Details</button>
+<div id="details" style="display:none">
+ <span><a id="details-channel"></a> &ensp; 👍 <span id="details-likes"></span></span>
<br>
- <div id="comments" style="display:none">
- <h3>Comments:</h3>
- <template id="template-comment">
- <div class="comment">
- <a class="author"></a>
- <span class="date"></span>
- <span class="date modified"></span>
- <div class="body"></div>
- <span>👍 <span class="likes"></span></span>
- <div class="replies"></div>
- </div>
- </template>
- </div>
-</body>
-</html>
+ <span><span id="details-views"></span> Views &ensp; <span id="details-date"></span></span>
+ <br>
+ <h3>Description:</h3>
+ <span id="details-desc">No description has been added to this video.</span>
+ <br>
+ <h4>Tags:</h4>
+ <div id="details-tags"></div>
+</div>
+<br>
+<div id="comments" style="display:none">
+ <h3>Comments:</h3>
+ <template id="template-comment">
+ <div class="comment">
+ <a class="author"></a>
+ <span class="date"></span>
+ <span class="date modified"></span>
+ <div class="body"></div>
+ <span>👍 <span class="likes"></span></span>
+ <div class="replies"></div>
+ </div>
+ </template>
+</div>
+{{ 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)
+ })
+}