0ad9e1e21db431a38ad484a8140574f18f216067
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/db/videos.go b/db/videos.go
2index 7cc3309c1eea425387856fbfa0f6fef85ae0abda..8dfad98b2defa98feed633c2d807003b3fd59977 100644
3--- a/db/videos.go
4+++ b/db/videos.go
5@@ -72,7 +72,7 @@ func (d *postgresDB) GetWatchedVideos(ctx context.Context, sortDesc bool) ([]mod
6 FROM videos
7 LEFT JOIN channels ON videos.channel_id=channels.id
8 WHERE watch_timestamp IS NOT NULL
9- ORDER BY watch_timestamp DESC
10+ ORDER BY watch_timestamp
11 `
12 if sortDesc {
13 query += " DESC"
14diff --git a/httpserver/ytrssil/server.go b/httpserver/ytrssil/server.go
15index 8c59f62b26792426b70e638d43bb0daff930bbc3..544c0b89bab920db8085a607e21e37322c3c45d6 100644
16--- a/httpserver/ytrssil/server.go
17+++ b/httpserver/ytrssil/server.go
18@@ -74,9 +74,11 @@ func SetupGinRouter(
19 pages.Use(auth.PageAuthMiddleware(cfg.AuthToken))
20 {
21 pages.GET("/", srv.NewVideosPage)
22+ pages.GET("/watched", srv.WatchedVideosPage)
23 pages.POST("/subscribe", srv.SubscribeToChannelPage)
24 pages.POST("/videos", srv.AddVideoPage)
25 pages.PATCH("/videos/:video_id/watch", srv.MarkVideoAsWatchedPage)
26+ pages.PATCH("/videos/:video_id/unwatch", srv.MarkVideoAsUnwatchedPage)
27 pages.PATCH("/videos/:video_id/progress", srv.SetVideoProgressPage)
28 }
29
30diff --git a/httpserver/ytrssil/videos_pages.go b/httpserver/ytrssil/videos_pages.go
31index 8200b219b93d771aa4c96dfdfab6683b74fb6c41..cf81b9d7bd090d5970059a86b2798e5fa7929bee 100644
32--- a/httpserver/ytrssil/videos_pages.go
33+++ b/httpserver/ytrssil/videos_pages.go
34@@ -21,6 +21,19 @@ func (srv server) NewVideosPage(c *gin.Context) {
35 })
36 }
37
38+func (srv server) WatchedVideosPage(c *gin.Context) {
39+ videos, err := srv.handler.GetWatchedVideos(c.Request.Context(), true)
40+ if err != nil {
41+ returnErr(c, http.StatusInternalServerError, err)
42+ return
43+ }
44+
45+ c.Render(http.StatusOK, pages.TemplRenderer{
46+ Ctx: c.Request.Context(),
47+ Component: pages.WatchedVideosPage(videos),
48+ })
49+}
50+
51 func (srv server) MarkVideoAsWatchedPage(c *gin.Context) {
52 err := srv.handler.MarkVideoAsWatched(c.Request.Context(), c.Param("video_id"))
53 if err != nil {
54@@ -31,6 +44,16 @@ func (srv server) MarkVideoAsWatchedPage(c *gin.Context) {
55 returnMsg(c, http.StatusOK, "")
56 }
57
58+func (srv server) MarkVideoAsUnwatchedPage(c *gin.Context) {
59+ err := srv.handler.MarkVideoAsUnwatched(c.Request.Context(), c.Param("video_id"))
60+ if err != nil {
61+ returnErr(c, http.StatusInternalServerError, err)
62+ return
63+ }
64+
65+ returnMsg(c, http.StatusOK, "")
66+}
67+
68 func (srv server) SetVideoProgressPage(c *gin.Context) {
69 progress := c.PostForm("progress")
70 if progress == "" {
71diff --git a/pages/auth.templ b/pages/auth.templ
72index 7f9d27fb9ececa6cda0bc7824d5c7fdb51a41d65..daf515468f7b51ddc988c42716cd98af9c0f8de9 100644
73--- a/pages/auth.templ
74+++ b/pages/auth.templ
75@@ -1,7 +1,7 @@
76 package pages
77
78 templ AuthPage(errorMsg string) {
79- @BaseLayout("ytrssil - Authentication") {
80+ @BaseLayout("ytrssil - Authentication", false) {
81 <div class="row justify-content-center mt-5">
82 <div class="col-md-6 col-lg-4">
83 <div class="card">
84diff --git a/pages/auth_templ.go b/pages/auth_templ.go
85index c0ad6766c0943f40c920a670a2d416c007df1b92..c49afe51c2d07b4e3bac09addfbea1d211668f31 100644
86--- a/pages/auth_templ.go
87+++ b/pages/auth_templ.go
88@@ -70,7 +70,7 @@ func AuthPage(errorMsg string) templ.Component {
89 }
90 return nil
91 })
92- templ_7745c5c3_Err = BaseLayout("ytrssil - Authentication").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
93+ templ_7745c5c3_Err = BaseLayout("ytrssil - Authentication", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
94 if templ_7745c5c3_Err != nil {
95 return templ_7745c5c3_Err
96 }
97diff --git a/pages/main.templ b/pages/main.templ
98index 5e6066c1607c3cf9ebc17842664cad3bf5734fff..676e1fec14d1b3e37ccba59c3408027f67a24a43 100644
99--- a/pages/main.templ
100+++ b/pages/main.templ
101@@ -1,6 +1,6 @@
102 package pages
103
104-templ BaseLayout(title string) {
105+templ BaseLayout(title string, isWatchedPage bool) {
106 <!DOCTYPE html>
107 <html lang="en" data-bs-theme="dark">
108 <head>
109@@ -28,7 +28,7 @@ templ BaseLayout(title string) {
110 <body>
111 @subscriptionModal()
112 @addVideoModal()
113- @navbar()
114+ @navbar(isWatchedPage)
115 <div class="container-fluid">
116 { children... }
117 </div>
118diff --git a/pages/main_templ.go b/pages/main_templ.go
119index 8b39fb0635b20110ef1d3891e48979d272f6f8c7..b9f17548503ee5fc741cae1cc0901ca5a6c67615 100644
120--- a/pages/main_templ.go
121+++ b/pages/main_templ.go
122@@ -8,7 +8,7 @@ package pages
123 import "github.com/a-h/templ"
124 import templruntime "github.com/a-h/templ/runtime"
125
126-func BaseLayout(title string) templ.Component {
127+func BaseLayout(title string, isWatchedPage bool) templ.Component {
128 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
129 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
130 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
131@@ -54,7 +54,7 @@ func BaseLayout(title string) templ.Component {
132 if templ_7745c5c3_Err != nil {
133 return templ_7745c5c3_Err
134 }
135- templ_7745c5c3_Err = navbar().Render(ctx, templ_7745c5c3_Buffer)
136+ templ_7745c5c3_Err = navbar(isWatchedPage).Render(ctx, templ_7745c5c3_Buffer)
137 if templ_7745c5c3_Err != nil {
138 return templ_7745c5c3_Err
139 }
140diff --git a/pages/navbar.templ b/pages/navbar.templ
141index 950e09bc795ed55887b76b507cf9d023d89f43bb..5828b0b16de926ba421b2df3640e47751fe5dbef 100644
142--- a/pages/navbar.templ
143+++ b/pages/navbar.templ
144@@ -1,6 +1,6 @@
145 package pages
146
147-templ navbar() {
148+templ navbar(isWatchedPage bool) {
149 <nav class="navbar navbar-expand-lg bg-body-tertiary">
150 <div class="container-fluid">
151 <a class="navbar-brand" href="/">
152@@ -32,6 +32,17 @@ templ navbar() {
153 Add Video <i class="bi bi-camera-video"></i>
154 </button>
155 </li>
156+ <li class="nav-item me-md-2 mt-2 mt-md-0">
157+ if isWatchedPage {
158+ <a href="/" class="btn btn-outline-secondary">
159+ New Videos <i class="bi bi-collection-play"></i>
160+ </a>
161+ } else {
162+ <a href="/watched" class="btn btn-outline-secondary">
163+ Watched <i class="bi bi-check-circle"></i>
164+ </a>
165+ }
166+ </li>
167 </ul>
168 <div class="d-flex" role="search">
169 <input id="video-search" class="form-control" type="search" placeholder="Search videos..."/>
170diff --git a/pages/navbar_templ.go b/pages/navbar_templ.go
171index 9f020fc85b1250d27ecd7e30f90982a5631ca897..133e9cff43c2cae5ad7100d7ab177c9f537b83f9 100644
172--- a/pages/navbar_templ.go
173+++ b/pages/navbar_templ.go
174@@ -8,7 +8,7 @@ package pages
175 import "github.com/a-h/templ"
176 import templruntime "github.com/a-h/templ/runtime"
177
178-func navbar() templ.Component {
179+func navbar(isWatchedPage bool) templ.Component {
180 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
181 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
182 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
183@@ -29,7 +29,22 @@ func navbar() templ.Component {
184 templ_7745c5c3_Var1 = templ.NopComponent
185 }
186 ctx = templ.ClearChildren(ctx)
187diff --git a/pages/new_videos.templ b/pages/new_videos.templ
188index 80b0651998b085238b76aecda0905f76c6999edb..036c9704137c4308bd2414a41d3372a2b02bce2b 100644
189--- a/pages/new_videos.templ
190+++ b/pages/new_videos.templ
191@@ -100,7 +100,7 @@ templ videoCard(video models.Video) {
192 }
193
194 templ NewVideosPage(videos []models.Video) {
195- @BaseLayout("ytrssil - New Videos") {
196+ @BaseLayout("ytrssil - New Videos", false) {
197 <div class="row">
198 for _, video := range videos {
199 <div class="video-card col-md-3 p-2" data-title={ video.Title } data-channel-name={ video.ChannelName }>
200diff --git a/pages/new_videos_templ.go b/pages/new_videos_templ.go
201index 5a1701ede5fceb27054bcf260e26623856aa5403..4bb5e32fdc39129f11b9ea7a58c616caf6c069e8 100644
202--- a/pages/new_videos_templ.go
203+++ b/pages/new_videos_templ.go
204@@ -436,7 +436,7 @@ func NewVideosPage(videos []models.Video) templ.Component {
205 }
206 return nil
207 })
208- templ_7745c5c3_Err = BaseLayout("ytrssil - New Videos").Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer)
209+ templ_7745c5c3_Err = BaseLayout("ytrssil - New Videos", false).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer)
210 if templ_7745c5c3_Err != nil {
211 return templ_7745c5c3_Err
212 }
213diff --git a/pages/watched_videos.templ b/pages/watched_videos.templ
214new file mode 100644
215index 0000000000000000000000000000000000000000..27f8f6019febe01437d69e920923f3d59dfc25ba
216--- /dev/null
217+++ b/pages/watched_videos.templ
218@@ -0,0 +1,48 @@
219+package pages
220+
221+import (
222+ "fmt"
223+
224+ "github.com/TheEdgeOfRage/ytrssil-api/models"
225+)
226+
227+templ watchedVideoCard(video models.Video) {
228+ <div class="card">
229+ @thumbnail(video)
230+ <div class="card-body">
231+ <p class="card-title text-truncate mb-0">{ video.Title }</p>
232+ <p class="d-flex justify-content-between">
233+ <a
234+ target="blank"
235+ href={ fmt.Sprintf("https://www.youtube.com/channel/%s", video.ChannelID) }
236+ class="card-text fw-light link-light link-underline-opacity-0"
237+ >{ video.ChannelName }</a>
238+ <span>
239+ { video.HumanizedPublishTime() }
240+ </span>
241+ </p>
242+ <div class="d-flex justify-content-end">
243+ <button
244+ hx-patch={ fmt.Sprintf("/videos/%s/unwatch", video.ID) }
245+ hx-target="closest .video-card"
246+ hx-swap="outerHTML"
247+ class="btn btn-secondary"
248+ >
249+ <i class="bi bi-arrow-counterclockwise"></i>
250+ </button>
251+ </div>
252+ </div>
253+ </div>
254+}
255+
256+templ WatchedVideosPage(videos []models.Video) {
257+ @BaseLayout("ytrssil - Watched Videos", true) {
258+ <div class="row">
259+ for _, video := range videos {
260+ <div class="video-card col-md-3 p-2" data-title={ video.Title } data-channel-name={ video.ChannelName }>
261+ @watchedVideoCard(video)
262+ </div>
263+ }
264+ </div>
265+ }
266+}
267diff --git a/pages/watched_videos_templ.go b/pages/watched_videos_templ.go
268new file mode 100644
269index 0000000000000000000000000000000000000000..02000188c8e55176cdf4438cbfae65e0ade657eb
270--- /dev/null
271+++ b/pages/watched_videos_templ.go
272@@ -0,0 +1,210 @@
273+// Code generated by templ - DO NOT EDIT.
274+
275+// templ: version: v0.3.960
276+package pages
277+
278+//lint:file-ignore SA4006 This context is only used if a nested component is present.
279+
280+import "github.com/a-h/templ"
281+import templruntime "github.com/a-h/templ/runtime"
282+
283+import (
284+ "fmt"
285+
286+ "github.com/TheEdgeOfRage/ytrssil-api/models"
287+)
288+
289+func watchedVideoCard(video models.Video) templ.Component {
290+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
291+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
292+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
293+ return templ_7745c5c3_CtxErr
294+ }
295+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
296+ if !templ_7745c5c3_IsBuffer {
297+ defer func() {
298+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
299+ if templ_7745c5c3_Err == nil {
300+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
301+ }
302+ }()
303+ }
304+ ctx = templ.InitializeContext(ctx)
305+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
306+ if templ_7745c5c3_Var1 == nil {
307+ templ_7745c5c3_Var1 = templ.NopComponent
308+ }
309+ ctx = templ.ClearChildren(ctx)
310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"card\">")
311+ if templ_7745c5c3_Err != nil {
312+ return templ_7745c5c3_Err
313+ }
314+ templ_7745c5c3_Err = thumbnail(video).Render(ctx, templ_7745c5c3_Buffer)
315+ if templ_7745c5c3_Err != nil {
316+ return templ_7745c5c3_Err
317+ }
318+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"card-body\"><p class=\"card-title text-truncate mb-0\">")
319+ if templ_7745c5c3_Err != nil {
320+ return templ_7745c5c3_Err
321+ }
322+ var templ_7745c5c3_Var2 string
323+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(video.Title)
324+ if templ_7745c5c3_Err != nil {
325+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/watched_videos.templ`, Line: 13, Col: 57}
326+ }
327+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
328+ if templ_7745c5c3_Err != nil {
329+ return templ_7745c5c3_Err
330+ }
331+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p><p class=\"d-flex justify-content-between\"><a target=\"blank\" href=\"")
332+ if templ_7745c5c3_Err != nil {
333+ return templ_7745c5c3_Err
334+ }
335+ var templ_7745c5c3_Var3 templ.SafeURL
336+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(fmt.Sprintf("https://www.youtube.com/channel/%s", video.ChannelID))
337+ if templ_7745c5c3_Err != nil {
338+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/watched_videos.templ`, Line: 17, Col: 78}
339+ }
340+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
341+ if templ_7745c5c3_Err != nil {
342+ return templ_7745c5c3_Err
343+ }
344+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"card-text fw-light link-light link-underline-opacity-0\">")
345+ if templ_7745c5c3_Err != nil {
346+ return templ_7745c5c3_Err
347+ }
348+ var templ_7745c5c3_Var4 string
349+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(video.ChannelName)
350+ if templ_7745c5c3_Err != nil {
351+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/watched_videos.templ`, Line: 19, Col: 24}
352+ }
353+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
354+ if templ_7745c5c3_Err != nil {
355+ return templ_7745c5c3_Err
356+ }
357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</a> <span>")
358+ if templ_7745c5c3_Err != nil {
359+ return templ_7745c5c3_Err
360+ }
361+ var templ_7745c5c3_Var5 string
362+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(video.HumanizedPublishTime())
363+ if templ_7745c5c3_Err != nil {
364+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/watched_videos.templ`, Line: 21, Col: 35}
365+ }
366+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
367+ if templ_7745c5c3_Err != nil {
368+ return templ_7745c5c3_Err
369+ }
370+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></p><div class=\"d-flex justify-content-end\"><button hx-patch=\"")
371+ if templ_7745c5c3_Err != nil {