Skip to content

Commit ab19d98

Browse files
committed
Add support for linking to syndicated content
1 parent 24b0d89 commit ab19d98

19 files changed

Lines changed: 505 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/.idea
2+
/.zed
23
/.postgres
34
/tsdata
45
*~
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package handlers
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strconv"
7+
8+
"chameth.com/chameth.com/cmd/serve/admin/templates"
9+
"chameth.com/chameth.com/cmd/serve/db"
10+
)
11+
12+
func ListSyndicationsHandler() func(http.ResponseWriter, *http.Request) {
13+
return func(w http.ResponseWriter, r *http.Request) {
14+
unpublished, err := db.GetUnpublishedSyndications()
15+
if err != nil {
16+
http.Error(w, "Failed to retrieve unpublished syndications", http.StatusInternalServerError)
17+
return
18+
}
19+
20+
syndications, err := db.GetAllSyndications()
21+
if err != nil {
22+
http.Error(w, "Failed to retrieve syndications", http.StatusInternalServerError)
23+
return
24+
}
25+
26+
unpublishedSummaries := make([]templates.SyndicationSummary, len(unpublished))
27+
for i, s := range unpublished {
28+
unpublishedSummaries[i] = templates.SyndicationSummary{
29+
ID: s.ID,
30+
Path: s.Path,
31+
ExternalURL: s.ExternalURL,
32+
Name: s.Name,
33+
Published: s.Published,
34+
}
35+
}
36+
37+
syndicationSummaries := make([]templates.SyndicationSummary, len(syndications))
38+
for i, s := range syndications {
39+
syndicationSummaries[i] = templates.SyndicationSummary{
40+
ID: s.ID,
41+
Path: s.Path,
42+
ExternalURL: s.ExternalURL,
43+
Name: s.Name,
44+
Published: s.Published,
45+
}
46+
}
47+
48+
data := templates.ListSyndicationsData{
49+
Unpublished: unpublishedSummaries,
50+
Syndications: syndicationSummaries,
51+
}
52+
53+
if err := templates.RenderListSyndications(w, data); err != nil {
54+
http.Error(w, "Failed to render template", http.StatusInternalServerError)
55+
}
56+
}
57+
}
58+
59+
func EditSyndicationHandler() func(http.ResponseWriter, *http.Request) {
60+
return func(w http.ResponseWriter, r *http.Request) {
61+
idStr := r.PathValue("id")
62+
id, err := strconv.Atoi(idStr)
63+
if err != nil {
64+
http.Error(w, "Invalid syndication ID", http.StatusBadRequest)
65+
return
66+
}
67+
68+
syndication, err := db.GetSyndicationByID(id)
69+
if err != nil {
70+
http.Error(w, "Syndication not found", http.StatusNotFound)
71+
return
72+
}
73+
74+
data := templates.EditSyndicationData{
75+
ID: syndication.ID,
76+
Path: syndication.Path,
77+
ExternalURL: syndication.ExternalURL,
78+
Name: syndication.Name,
79+
Published: syndication.Published,
80+
}
81+
82+
if err := templates.RenderEditSyndication(w, data); err != nil {
83+
http.Error(w, "Failed to render template", http.StatusInternalServerError)
84+
}
85+
}
86+
}
87+
88+
func CreateSyndicationHandler() func(http.ResponseWriter, *http.Request) {
89+
return func(w http.ResponseWriter, r *http.Request) {
90+
if err := r.ParseForm(); err != nil {
91+
http.Error(w, "Failed to parse form", http.StatusBadRequest)
92+
return
93+
}
94+
95+
path := r.FormValue("path")
96+
externalURL := r.FormValue("external_url")
97+
name := r.FormValue("name")
98+
99+
if path == "" || externalURL == "" || name == "" {
100+
http.Error(w, "Path, external URL, and name are required", http.StatusBadRequest)
101+
return
102+
}
103+
104+
id, err := db.CreateSyndication(path, externalURL, name)
105+
if err != nil {
106+
http.Error(w, "Failed to create syndication", http.StatusInternalServerError)
107+
return
108+
}
109+
110+
http.Redirect(w, r, fmt.Sprintf("/syndications/edit/%d", id), http.StatusSeeOther)
111+
}
112+
}
113+
114+
func UpdateSyndicationHandler() func(http.ResponseWriter, *http.Request) {
115+
return func(w http.ResponseWriter, r *http.Request) {
116+
idStr := r.PathValue("id")
117+
id, err := strconv.Atoi(idStr)
118+
if err != nil {
119+
http.Error(w, "Invalid syndication ID", http.StatusBadRequest)
120+
return
121+
}
122+
123+
if err := r.ParseForm(); err != nil {
124+
http.Error(w, "Failed to parse form", http.StatusBadRequest)
125+
return
126+
}
127+
128+
path := r.FormValue("path")
129+
externalURL := r.FormValue("external_url")
130+
name := r.FormValue("name")
131+
published := r.FormValue("published") == "true"
132+
133+
if err := db.UpdateSyndication(id, path, externalURL, name, published); err != nil {
134+
http.Error(w, "Failed to update syndication", http.StatusInternalServerError)
135+
return
136+
}
137+
138+
http.Redirect(w, r, fmt.Sprintf("/syndications/edit/%d", id), http.StatusSeeOther)
139+
}
140+
}
141+
142+
func DeleteSyndicationHandler() func(http.ResponseWriter, *http.Request) {
143+
return func(w http.ResponseWriter, r *http.Request) {
144+
idStr := r.PathValue("id")
145+
id, err := strconv.Atoi(idStr)
146+
if err != nil {
147+
http.Error(w, "Invalid syndication ID", http.StatusBadRequest)
148+
return
149+
}
150+
151+
if err := db.DeleteSyndication(id); err != nil {
152+
http.Error(w, "Failed to delete syndication", http.StatusInternalServerError)
153+
return
154+
}
155+
156+
http.Redirect(w, r, "/syndications", http.StatusSeeOther)
157+
}
158+
}

cmd/serve/admin/tailscale.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ func Start() error {
112112
httpsMux.HandleFunc("GET /video-game-reviews/edit/{id}", handlers.EditVideoGameReviewHandler())
113113
httpsMux.HandleFunc("POST /video-game-reviews/create/{id}", handlers.CreateVideoGameReviewHandler())
114114
httpsMux.HandleFunc("POST /video-game-reviews/edit/{id}", handlers.UpdateVideoGameReviewHandler())
115+
httpsMux.HandleFunc("GET /syndications", handlers.ListSyndicationsHandler())
116+
httpsMux.HandleFunc("POST /syndications/create", handlers.CreateSyndicationHandler())
117+
httpsMux.HandleFunc("GET /syndications/edit/{id}", handlers.EditSyndicationHandler())
118+
httpsMux.HandleFunc("POST /syndications/edit/{id}", handlers.UpdateSyndicationHandler())
119+
httpsMux.HandleFunc("POST /syndications/delete/{id}", handlers.DeleteSyndicationHandler())
115120

116121
httpsServer := &http.Server{
117122
Handler: middleware.Chain(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package templates
2+
3+
import (
4+
"html/template"
5+
"net/http"
6+
)
7+
8+
var editSyndicationTemplate = template.Must(
9+
template.
10+
New("page.html.gotpl").
11+
ParseFS(
12+
templates,
13+
"page.html.gotpl",
14+
"edit-syndication.html.gotpl",
15+
),
16+
)
17+
18+
type EditSyndicationData struct {
19+
PageData
20+
ID int
21+
Path string
22+
ExternalURL string
23+
Name string
24+
Published bool
25+
}
26+
27+
func RenderEditSyndication(w http.ResponseWriter, data EditSyndicationData) error {
28+
return editSyndicationTemplate.Execute(w, data)
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{{/* gotype: chameth.com/chameth.com/cmd/serve/admin/templates.EditSyndicationData */}}
2+
{{define "content"}}
3+
<h2>Edit syndication: {{.Name}}</h2>
4+
<a href="https://chameth.com{{.Path}}">View on chameth.com</a>
5+
<form action="/syndications/edit/{{.ID}}" method="post">
6+
<label>Path: <input type="text" name="path" value="{{.Path}}" /></label>
7+
<label>External URL: <input type="text" name="external_url" value="{{.ExternalURL}}" /></label>
8+
<label>Name: <input type="text" name="name" value="{{.Name}}" /></label>
9+
<label>
10+
<input type="checkbox" name="published" value="true" {{if .Published}}checked{{end}} />
11+
Published
12+
</label>
13+
<input type="submit" value="Save" />
14+
</form>
15+
<form action="/syndications/delete/{{.ID}}" method="post" onsubmit="return confirm('Are you sure you want to delete this syndication?');">
16+
<button type="submit">Delete</button>
17+
</form>
18+
<a href="/syndications">Back to list</a>
19+
{{end}}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package templates
2+
3+
import (
4+
"html/template"
5+
"net/http"
6+
)
7+
8+
var listSyndicationsTemplate = template.Must(
9+
template.
10+
New("page.html.gotpl").
11+
ParseFS(
12+
templates,
13+
"page.html.gotpl",
14+
"list-syndications.html.gotpl",
15+
),
16+
)
17+
18+
type ListSyndicationsData struct {
19+
PageData
20+
Unpublished []SyndicationSummary
21+
Syndications []SyndicationSummary
22+
}
23+
24+
type SyndicationSummary struct {
25+
ID int
26+
Path string
27+
ExternalURL string
28+
Name string
29+
Published bool
30+
}
31+
32+
func RenderListSyndications(w http.ResponseWriter, data ListSyndicationsData) error {
33+
return listSyndicationsTemplate.Execute(w, data)
34+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{{/* gotype: chameth.com/chameth.com/cmd/serve/admin/templates.ListSyndicationsData */}}
2+
{{define "content"}}
3+
<form method="POST" action="/syndications/create">
4+
<label>Path: <input type="text" name="path" placeholder="/posts/my-post/" /></label>
5+
<label>External URL: <input type="text" name="external_url" placeholder="https://medium.com/@user/post" /></label>
6+
<label>Name: <input type="text" name="name" placeholder="Medium" /></label>
7+
<button type="submit">Create Syndication</button>
8+
</form>
9+
10+
{{if .Unpublished}}
11+
<h2>Drafts</h2>
12+
<table>
13+
<thead>
14+
<tr>
15+
<th>ID</th>
16+
<th>Path</th>
17+
<th>Name</th>
18+
<th>External URL</th>
19+
<th>Actions</th>
20+
</tr>
21+
</thead>
22+
<tbody>
23+
{{range .Unpublished}}
24+
<tr>
25+
<td>{{.ID}}</td>
26+
<td>{{.Path}}</td>
27+
<td>{{.Name}}</td>
28+
<td>{{.ExternalURL}}</td>
29+
<td><a href="/syndications/edit/{{.ID}}">Edit</a></td>
30+
</tr>
31+
{{end}}
32+
</tbody>
33+
</table>
34+
{{end}}
35+
36+
<h2>Syndications</h2>
37+
<table>
38+
<thead>
39+
<tr>
40+
<th>ID</th>
41+
<th>Path</th>
42+
<th>Name</th>
43+
<th>External URL</th>
44+
<th>Actions</th>
45+
</tr>
46+
</thead>
47+
<tbody>
48+
{{range .Syndications}}
49+
<tr>
50+
<td>{{.ID}}</td>
51+
<td>{{.Path}}</td>
52+
<td>{{.Name}}</td>
53+
<td>{{.ExternalURL}}</td>
54+
<td><a href="/syndications/edit/{{.ID}}">Edit</a></td>
55+
</tr>
56+
{{end}}
57+
</tbody>
58+
</table>
59+
{{end}}

cmd/serve/admin/templates/page.html.gotpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<a href="/films">Films</a>
2323
<a href="/film-lists">Film Lists</a>
2424
<a href="/videogames">Video Games</a>
25+
<a href="/syndications">Syndications</a>
2526
<a href="/media">Media</a>
2627
</nav>
2728
</header>

cmd/serve/assets/stylesheet.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var includeOrder = []string{
3131
"prints.css",
3232
"projects.css",
3333
"snippets.css",
34+
"syndication.css",
3435
"syntax.css",
3536
"tables.css",
3637
"typography.css",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.syndications {
2+
display: flex;
3+
flex-direction: row;
4+
flex-wrap: wrap;
5+
gap: var(--small-space);
6+
color: var(--text-alt-colour);
7+
8+
ul {
9+
display: contents;
10+
list-style-type: none;
11+
}
12+
13+
p,
14+
ul,
15+
li {
16+
margin: 0;
17+
padding: 0;
18+
}
19+
}

0 commit comments

Comments
 (0)