Diff
1diff --git a/db/channels.go b/db/channels.go
2index 79073cfca542df8d37f841979cf16cf535f3b6ef..7d90c2fc5abff2a8ea1489e52d696ce3b88785cf 100644
3--- a/db/channels.go
4+++ b/db/channels.go
5@@ -63,6 +63,9 @@ func (db *postgresDB) ListChannels(ctx context.Context) ([]models.Channel, error
6 }
7 channels = append(channels, channel)
8 }
9+ if err := rows.Err(); err != nil {
10+ return nil, err
11+ }
12
13 return channels, nil
14 }
15diff --git a/db/videos.go b/db/videos.go
16index c03c97ee056024b390cf1af59cc16ca3017dea7b..0c9dda467e85bf108cd429363f2d235c3320d1a4 100644
17--- a/db/videos.go
18+++ b/db/videos.go
19@@ -67,6 +67,9 @@ func (db *postgresDB) GetNewVideos(ctx context.Context, sortDesc bool) ([]models
20 }
21 videos = append(videos, video)
22 }
23+ if err := rows.Err(); err != nil {
24+ return nil, err
25+ }
26
27 return videos, nil
28 }
29@@ -135,6 +138,9 @@ func (db *postgresDB) GetWatchedVideos(
30 }
31 videos = append(videos, video)
32 }
33+ if err := rows.Err(); err != nil {
34+ return nil, err
35+ }
36
37 return videos, nil
38 }
39@@ -391,6 +397,9 @@ func (db *postgresDB) GetVideosForCleanup(ctx context.Context, olderThan time.Du
40 }
41 videos = append(videos, video)
42 }
43+ if err := rows.Err(); err != nil {
44+ return nil, err
45+ }
46
47 return videos, nil
48 }
49@@ -415,6 +424,9 @@ func (db *postgresDB) GetLiveVideos(ctx context.Context) ([]models.Video, error)
50 }
51 videos = append(videos, video)
52 }
53+ if err := rows.Err(); err != nil {
54+ return nil, err
55+ }
56
57 return videos, nil
58 }
59diff --git a/feedparser/feedparser.go b/feedparser/feedparser.go
60index a322638f9718ee399fa7965f3e92ff68aa0a2611..6dc33cc23dbdec2bcd2439729e5da2abc25b20b0 100644
61--- a/feedparser/feedparser.go
62+++ b/feedparser/feedparser.go
63@@ -34,7 +34,7 @@ func NewParser(l *slog.Logger) *parser {
64 }
65
66 func (p *parser) fetch(url string) (io.ReadCloser, error) {
67- req, err := http.NewRequest("GET", url, nil)
68+ req, err := http.NewRequest(http.MethodGet, url, nil)
69 if err != nil {
70 p.log.Error("Failed to create request", "call", "http.NewRequest", "error", err)
71 return nil, err
72@@ -47,8 +47,10 @@ func (p *parser) fetch(url string) (io.ReadCloser, error) {
73 }
74
75 if response.StatusCode == http.StatusNotFound {
76+ response.Body.Close()
77 return nil, fmt.Errorf("%w: %s", ErrInvalidChannelID, url)
78 } else if response.StatusCode != http.StatusOK {
79+ response.Body.Close()
80 return nil, fmt.Errorf("failed to get feed with status %d", response.StatusCode)
81 }
82
83diff --git a/handler/downloads.go b/handler/downloads.go
84index f85965cfad5d357bb26eb44d02607f5a25274c07..0d0fb2632231f42c63a1433b9f101a3029b245d4 100644
85--- a/handler/downloads.go
86+++ b/handler/downloads.go
87@@ -91,7 +91,9 @@ func (h *handler) ServeVideoFile(ctx context.Context, videoID string) (filePath
88 }
89
90 if _, err := os.Stat(*video.FilePath); os.IsNotExist(err) {
91- h.db.DeleteVideoFile(ctx, videoID)
92+ if dbErr := h.db.DeleteVideoFile(ctx, videoID); dbErr != nil {
93+ h.log.Error("Failed to clear missing file from database", "video_id", videoID, "error", dbErr)
94+ }
95 return "", "", fmt.Errorf("file not found on disk")
96 }
97
98diff --git a/handler/videos.go b/handler/videos.go
99index 409338214bce1a3b47f1ce1074762dc8c2b9c64c..8a62810becffc139771626cac4fc2e37b4fc2da4 100644
100--- a/handler/videos.go
101+++ b/handler/videos.go
102@@ -231,7 +231,7 @@ func (h *handler) SetVideoProgress(ctx context.Context, videoID string, progress
103
104 video, err := h.db.SetVideoProgress(ctx, videoID, int(progress.Seconds()))
105 if err != nil {
106- return nil, fmt.Errorf("%w: %v", ErrInvalidProgress, err.Error())
107+ return nil, fmt.Errorf("set video progress: %w", err)
108 }
109
110 return video, nil
111diff --git a/httpserver/auth/auth.go b/httpserver/auth/auth.go
112index aae426d79d6a5e982b7bfd9326e070a5dc7d585e..f53b29d2cffce072c620d2a3af12be23b2b97e07 100644
113--- a/httpserver/auth/auth.go
114+++ b/httpserver/auth/auth.go
115@@ -12,10 +12,12 @@ func PageAuthMiddleware(authToken string) gin.HandlerFunc {
116 tokenCookie, err := c.Request.Cookie("token")
117 if err != nil {
118 c.Redirect(http.StatusFound, "/auth")
119+ c.Abort()
120 return
121 }
122 if tokenCookie.Value != authToken {
123 c.Redirect(http.StatusFound, "/auth")
124+ c.Abort()
125 return
126 }
127
128diff --git a/httpserver/auth/auth_test.go b/httpserver/auth/auth_test.go
129index c6cb38a7f0ef538cebf49cec477484833a7d01ff..1d978e816b5a439274de665fc820d4f2c3a3ad4c 100644
130--- a/httpserver/auth/auth_test.go
131+++ b/httpserver/auth/auth_test.go
132@@ -67,3 +67,21 @@ func (s *AuthTestSuite) TestWrongCredentials() {
133 s.Equal(http.StatusUnauthorized, w.Code)
134 s.Equal(`{"error":"invalid auth token"}`, w.Body.String())
135 }
136+
137+func (s *AuthTestSuite) TestPageAuthenticationStopsHandler() {
138+ engine := gin.New()
139+ handlerCalled := false
140+ engine.Use(PageAuthMiddleware(s.cfg.AuthToken))
141+ engine.POST("/", func(c *gin.Context) {
142+ handlerCalled = true
143+ c.Status(http.StatusNoContent)
144+ })
145+
146+ w := httptest.NewRecorder()
147+ req, _ := http.NewRequest(http.MethodPost, "/", nil)
148+ engine.ServeHTTP(w, req)
149+
150+ s.Equal(http.StatusFound, w.Code)
151+ s.Equal("/auth", w.Header().Get("Location"))
152+ s.False(handlerCalled)
153+}
154diff --git a/httpserver/ytrssil/videos.go b/httpserver/ytrssil/videos.go
155index 19c616e26a6e731cecc3a3c8649f9f1cf836bb07..acb6793b78298836275c141343398e01872d0a24 100644
156--- a/httpserver/ytrssil/videos.go
157+++ b/httpserver/ytrssil/videos.go
158@@ -138,7 +138,7 @@ func (srv *server) DownloadVideoJSON(c *gin.Context) {
159 return
160 }
161
162- if !isSupportedDownloadFormat(request.Format) {
163+ if request.Format <= 0 {
164 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid download format"})
165 return
166 }
167@@ -162,12 +162,3 @@ func (srv *server) ServeVideoFileJSON(c *gin.Context) {
168 c.Header("Content-Disposition", "attachment; filename=\""+filename+"\"")
169 c.File(filePath)
170 }
171-
172-func isSupportedDownloadFormat(format int) bool {
173- switch format {
174- case 480, 720, 1080, 1440, 2160:
175- return true
176- default:
177- return false
178- }
179-}
180diff --git a/lib/clients/youtube/is_short.go b/lib/clients/youtube/is_short.go
181index a331e6485c72720f555515f348e45c8047228107..51545f867e641d5932e360d4421dcde2a28f4516 100644
182--- a/lib/clients/youtube/is_short.go
183+++ b/lib/clients/youtube/is_short.go
184@@ -28,6 +28,7 @@ func (c *youTubeClient) isShort(ctx context.Context, videoID string) (bool, erro
185 c.log.Error("Failed to check if a video is a short", "videoID", videoID, "error", err)
186 return false, err
187 }
188+ defer resp.Body.Close()
189
190 return resp.StatusCode == http.StatusOK, nil
191 }
192diff --git a/models/video.go b/models/video.go
193index 73fb15b7edea7324797e80a49df04b2bfd0d0ecf..d843c078895597b12f4658944c982e197b0e49a0 100644
194--- a/models/video.go
195+++ b/models/video.go
196@@ -43,7 +43,18 @@ type Video struct {
197
198 // ProgressPercentage returns the current progress of the video as an integer from 0-100
199 func (v Video) ProgressPercentage() int {
200- return int(100 * float64(v.ProgressSeconds) / float64(v.DurationSeconds))
201+ if v.DurationSeconds <= 0 {
202+ return 0
203+ }
204+
205+ percentage := 100 * v.ProgressSeconds / v.DurationSeconds
206+ if percentage < 0 {
207+ return 0
208+ }
209+ if percentage > 100 {
210+ return 100
211+ }
212+ return percentage
213 }
214
215 // WatchURL returns the formatted YouTube watch URL including the timestamp pointing to the current progress