From 28e8b322d005dbeb2f149a6f6d4fb2b0cc63fe52 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Wed, 13 May 2026 18:22:18 +0200 Subject: [PATCH] feat: add watch-complete endpoint Removes continue_watching_entry and clears progress when the last episode finishes so it no longer shows in Continue Watching. --- internal/domain/playback.go | 2 ++ internal/playback/handler/handler.go | 27 ++++++++++++++++++++++ internal/playback/repository/repository.go | 4 ++++ internal/playback/service/service.go | 13 +++++++++++ 4 files changed, 46 insertions(+) diff --git a/internal/domain/playback.go b/internal/domain/playback.go index 36286f9..27cc0db 100644 --- a/internal/domain/playback.go +++ b/internal/domain/playback.go @@ -8,6 +8,7 @@ import ( type PlaybackService interface { BuildWatchData(ctx context.Context, animeID int, titleCandidates []string, episode string, mode string, userID string) (map[string]any, error) SaveProgress(ctx context.Context, userID string, animeID int64, episode int, timeSeconds float64) error + CompleteAnime(ctx context.Context, userID string, animeID int64) error ResolveProxyToken(token string) (string, string, error) } @@ -35,4 +36,5 @@ type PlaybackRepository interface { GetContinueWatchingEntry(ctx context.Context, params db.GetContinueWatchingEntryParams) (db.ContinueWatchingEntry, error) SaveWatchProgress(ctx context.Context, params db.SaveWatchProgressParams) error UpsertContinueWatchingEntry(ctx context.Context, params db.UpsertContinueWatchingEntryParams) (db.ContinueWatchingEntry, error) + DeleteContinueWatchingEntry(ctx context.Context, params db.DeleteContinueWatchingEntryParams) error } diff --git a/internal/playback/handler/handler.go b/internal/playback/handler/handler.go index da96f2d..04753ca 100644 --- a/internal/playback/handler/handler.go +++ b/internal/playback/handler/handler.go @@ -36,6 +36,7 @@ func (h *PlaybackHandler) Register(r *gin.Engine) { log.Println("Registering playback routes") r.GET("/anime/:id/watch", h.HandleWatchPage) r.POST("/api/watch-progress", h.HandleSaveProgress) + r.POST("/api/watch-complete", h.HandleWatchComplete) r.GET("/api/watch/thumbnails/:animeId", h.HandleEpisodeThumbnails) r.GET("/watch/proxy/stream", h.HandleProxyStream) r.GET("/watch/proxy/subtitle", h.HandleProxySubtitle) @@ -111,6 +112,32 @@ func (h *PlaybackHandler) HandleSaveProgress(c *gin.Context) { c.Status(http.StatusOK) } +func (h *PlaybackHandler) HandleWatchComplete(c *gin.Context) { + user, _ := c.Get("User") + userID := "" + if u, ok := user.(*domain.User); ok { + userID = u.ID + } + + var req struct { + MalID int64 `json:"mal_id"` + Episode int `json:"episode"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.Status(http.StatusBadRequest) + return + } + + err := h.svc.CompleteAnime(c.Request.Context(), userID, req.MalID) + if err != nil { + c.Status(http.StatusInternalServerError) + return + } + + c.Status(http.StatusOK) +} + func (h *PlaybackHandler) HandleEpisodeThumbnails(c *gin.Context) { id, err := strconv.Atoi(c.Param("animeId")) if err != nil { diff --git a/internal/playback/repository/repository.go b/internal/playback/repository/repository.go index b0fa735..423df11 100644 --- a/internal/playback/repository/repository.go +++ b/internal/playback/repository/repository.go @@ -29,3 +29,7 @@ func (r *playbackRepository) SaveWatchProgress(ctx context.Context, params db.Sa func (r *playbackRepository) UpsertContinueWatchingEntry(ctx context.Context, params db.UpsertContinueWatchingEntryParams) (db.ContinueWatchingEntry, error) { return r.queries.UpsertContinueWatchingEntry(ctx, params) } + +func (r *playbackRepository) DeleteContinueWatchingEntry(ctx context.Context, params db.DeleteContinueWatchingEntryParams) error { + return r.queries.DeleteContinueWatchingEntry(ctx, params) +} diff --git a/internal/playback/service/service.go b/internal/playback/service/service.go index d3e1369..7ed986a 100644 --- a/internal/playback/service/service.go +++ b/internal/playback/service/service.go @@ -322,6 +322,19 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title }, nil } +func (s *playbackService) CompleteAnime(ctx context.Context, userID string, animeID int64) error { + _ = s.repo.DeleteContinueWatchingEntry(ctx, db.DeleteContinueWatchingEntryParams{ + UserID: userID, + AnimeID: animeID, + }) + return s.repo.SaveWatchProgress(ctx, db.SaveWatchProgressParams{ + UserID: userID, + AnimeID: animeID, + CurrentEpisode: sql.NullInt64{Valid: false}, + CurrentTimeSeconds: 0, + }) +} + func (s *playbackService) SaveProgress(ctx context.Context, userID string, animeID int64, episode int, timeSeconds float64) error { _, err := s.repo.UpsertContinueWatchingEntry(ctx, db.UpsertContinueWatchingEntryParams{ ID: uuid.New().String(),