Compare commits
2 Commits
76b550d628
...
9c7fa50fcd
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c7fa50fcd | |||
| e4c22dbe47 |
74
main.go
74
main.go
@@ -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,13 +357,48 @@ 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"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
||||||
http.Error(w, err.Error(), 500)
|
isFormData := r.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
|
||||||
return
|
|
||||||
|
if isFormData {
|
||||||
|
body.Feed = r.FormValue("feed")
|
||||||
|
} else {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.Feed == "" {
|
if body.Feed == "" {
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSON(w, struct {
|
if isFormData {
|
||||||
Ok bool `json:"ok"`
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
}{true}, 201)
|
} else {
|
||||||
|
sendJSON(w, struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
}{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
94
views/index.html
Normal 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
67
views/podcast.html
Normal 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>
|
||||||
Reference in New Issue
Block a user