Diff
1diff --git a/API.md b/API.md
2index 1dcb5c0639855a787894e53511e8a178fadb93b2..f720777c65f956abe40c1e66b2d7ac7d6c059a1e 100644
3--- a/API.md
4+++ b/API.md
5@@ -18,7 +18,7 @@ An incorrect token returns `401`:
6 {"error":"invalid auth token"}
7 ```
8
9-Handler failures return `500` with an `error` string.
10+Invalid request input returns `400` with an `error` string. Handler failures return `500` with an `error` string.
11
12 ## Video object
13
14@@ -97,3 +97,19 @@ Success response:
15 ```json
16 {"msg":"cleared video from watch history"}
17 ```
18+
19+### `POST /api/videos/:video_id/progress`
20+
21+Updates the saved watch progress. The request body must contain a `progress` string in Go duration, `mm:ss`, or `hh:mm:ss` format:
22+
23+```json
24+{"progress":"2:30"}
25+```
26+
27+Success response:
28+
29+```json
30+{"video":{/* Video object */}}
31+```
32+
33+Missing or invalid progress returns `400`.
34diff --git a/httpserver/ytrssil/server.go b/httpserver/ytrssil/server.go
35index 8c4b08395b55401db3f5a4ef136a953b6ea9ab67..219511b57e27a740933e5e8a5f1207338834584d 100644
36--- a/httpserver/ytrssil/server.go
37+++ b/httpserver/ytrssil/server.go
38@@ -98,6 +98,7 @@ func SetupGinRouter(
39 api.GET("videos/watched", srv.GetWatchedVideosJSON)
40 api.POST("videos/:video_id/watch", srv.MarkVideoAsWatchedJSON)
41 api.POST("videos/:video_id/unwatch", srv.MarkVideoAsUnwatchedJSON)
42+ api.POST("videos/:video_id/progress", srv.SetVideoProgressJSON)
43 api.POST("videos/:video_id/download", srv.DownloadVideoJSON)
44 }
45
46diff --git a/httpserver/ytrssil/videos.go b/httpserver/ytrssil/videos.go
47index 327795e039107f044d24d7e435075c4a5fcd4991..b436ada2187d7ec4244ce4d43099eddf196d9463 100644
48--- a/httpserver/ytrssil/videos.go
49+++ b/httpserver/ytrssil/videos.go
50@@ -1,10 +1,13 @@
51 package ytrssil
52
53 import (
54+ "errors"
55 "net/http"
56 "strconv"
57
58 "github.com/gin-gonic/gin"
59+
60+ "github.com/TheEdgeOfRage/ytrssil-api/handler"
61 )
62
63 func (srv *server) GetNewVideosJSON(c *gin.Context) {
64@@ -64,6 +67,32 @@ func (srv *server) MarkVideoAsUnwatchedJSON(c *gin.Context) {
65 c.JSON(http.StatusOK, gin.H{"msg": "cleared video from watch history"})
66 }
67
68+func (srv *server) SetVideoProgressJSON(c *gin.Context) {
69+ var request struct {
70+ Progress string `json:"progress"`
71+ }
72+ if err := c.ShouldBindJSON(&request); err != nil {
73+ c.JSON(http.StatusBadRequest, gin.H{"error": "invalid progress request"})
74+ return
75+ }
76+ if request.Progress == "" {
77+ c.JSON(http.StatusBadRequest, gin.H{"error": "missing progress"})
78+ return
79+ }
80+
81+ video, err := srv.handler.SetVideoProgress(c.Request.Context(), c.Param("video_id"), request.Progress)
82+ if err != nil {
83+ if errors.Is(err, handler.ErrInvalidProgress) {
84+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
85+ return
86+ }
87+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
88+ return
89+ }
90+
91+ c.JSON(http.StatusOK, gin.H{"video": video})
92+}
93+
94 func (srv *server) DownloadVideoJSON(c *gin.Context) {
95 videoID := c.Param("video_id")
96 resolution := c.PostForm("format")