Compare commits

...

2 Commits

Author SHA1 Message Date
9c7fa50fcd add podcast page 2026-02-13 17:14:02 +03:00
e4c22dbe47 add html page w/ list of podcasts 2026-02-13 17:04:32 +03:00
3 changed files with 229 additions and 6 deletions

62
main.go
View File

@@ -1,9 +1,11 @@
package main package main
import ( import (
"embed"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"html/template"
"io" "io"
"log" "log"
"net/http" "net/http"
@@ -223,7 +225,14 @@ func deletePodcastById(db *sqlx.DB, podcastId int64) error {
return nil return nil
} }
var (
//go:embed views
viewsFS embed.FS
)
func main() { func main() {
tmpl := template.Must(template.ParseFS(viewsFS, "**/*.html"))
podcastsDirPath := os.Getenv("PODCASTS_DIRPATH") podcastsDirPath := os.Getenv("PODCASTS_DIRPATH")
dbPath := os.Getenv("DB_PATH") dbPath := os.Getenv("DB_PATH")
@@ -324,6 +333,20 @@ func main() {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
podcasts, err := getPodcasts(db)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
tmpl.ExecuteTemplate(w, "index.html", struct {
Podcasts []*Podcast
}{
Podcasts: podcasts,
})
})
mux.HandleFunc("GET /podcasts", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("GET /podcasts", func(w http.ResponseWriter, r *http.Request) {
podcasts, err := getPodcasts(db) podcasts, err := getPodcasts(db)
if err != nil { if err != nil {
@@ -334,14 +357,49 @@ func main() {
sendJSON(w, podcasts, 200) sendJSON(w, podcasts, 200)
}) })
mux.HandleFunc("GET /podcasts/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, err.Error(), 404)
return
}
podcast, err := getPodcastById(db, int64(id))
if err != nil {
http.Error(w, err.Error(), 404)
return
}
episodes, err := getPodcastEpisodes(db, podcast.ID)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
tmpl.ExecuteTemplate(w, "podcast.html", struct {
Podcast *Podcast
Episodes []*Episode
}{
Podcast: podcast,
Episodes: episodes,
})
})
mux.HandleFunc("POST /podcasts", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("POST /podcasts", func(w http.ResponseWriter, r *http.Request) {
var body struct { var body struct {
Feed string `json:"feed"` Feed string `json:"feed"`
} }
isFormData := r.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
if isFormData {
body.Feed = r.FormValue("feed")
} else {
if err := json.NewDecoder(r.Body).Decode(&body); err != nil { if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
} }
}
if body.Feed == "" { if body.Feed == "" {
http.Error(w, "invalid request, rss feed url is required", 400) http.Error(w, "invalid request, rss feed url is required", 400)
@@ -363,9 +421,13 @@ func main() {
log.Printf("failed to create directory for podcast %s: %v\n", feed.Channel.Title, err) log.Printf("failed to create directory for podcast %s: %v\n", feed.Channel.Title, err)
} }
if isFormData {
http.Redirect(w, r, "/", http.StatusFound)
} else {
sendJSON(w, struct { sendJSON(w, struct {
Ok bool `json:"ok"` Ok bool `json:"ok"`
}{true}, 201) }{true}, 201)
}
}) })
mux.HandleFunc("GET /podcasts/{id}/episodes", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("GET /podcasts/{id}/episodes", func(w http.ResponseWriter, r *http.Request) {

94
views/index.html Normal file
View File

@@ -0,0 +1,94 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>podcaster</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
max-width: 1440px;
width: 100%;
margin: 0 auto;
padding: 8px;
}
.podcast-form {
margin-top: 12px;
display: flex;
gap: 4px;
}
.podcast-form__input {
width: 100%;
}
.podcast-form__btn {
white-space: nowrap;
}
.podcasts {
display: grid;
grid-template-columns: repeat(auto-fit, 250px);
gap: 12px;
margin-top: 20px;
}
.podcast {
display: block;
}
.podcast__cover {
width: 100%;
aspect-ratio: 1/1;
}
@media screen and (max-width: 1024px) {
.podcast-form {
flex-wrap: wrap;
}
.podcast-form__btn {
width: 100%;
}
}
@media screen and (max-width: 528px) {
.podcasts {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<h1>podcaster</h1>
<form method="POST" action="/podcasts" class="podcast-form">
<input
type="text"
inputmode="url"
placeholder="rss feed"
name="feed"
class="podcast-form__input"
/>
<button type="submit" class="podcast-form__btn">Add podcast</button>
</form>
<div class="podcasts">
{{range .Podcasts}}
<a href="/podcasts/{{.ID}}" class="podcast">
<img src="{{.Image}}" alt="" class="podcast__cover" />
<span>{{.Name}}</span>
</a>
{{end}}
</div>
</body>
</html>

67
views/podcast.html Normal file
View File

@@ -0,0 +1,67 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.Podcast.Name}}</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
max-width: 1440px;
width: 100%;
margin: 0 auto;
padding: 8px;
}
.header {
margin-top: 12px;
display: flex;
gap: 8px;
}
.header img {
width: 300px;
aspect-ration: 1/1;
}
.header .info {
display: flex;
flex-direction: column;
gap: 4px;
}
.episodes {
margin-top: 24px;
padding: 10px;
border-top: 1px solid;
}
</style>
</head>
<body>
<h1><a href="/">podcaster</a></h1>
<div class="header">
<img src="{{.Podcast.Image}}" alt="" />
<div class="info">
<h2>{{.Podcast.Name}}</h2>
<p>{{.Podcast.Description}}</p>
</div>
</div>
<div class="episodes">
{{range .Episodes}}
<div>
<span>{{.Title}}</span>
</div>
{{end}}
</div>
</body>
</html>