fix: complete anime at final episode

This commit is contained in:
2026-04-18 23:52:24 +02:00
parent e336e2aa40
commit 2849a91736
3 changed files with 99 additions and 0 deletions

View File

@@ -297,6 +297,83 @@ func (h *Handler) HandleSaveProgress(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
func (h *Handler) HandleCompleteAnime(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
user := middleware.GetUser(r.Context())
if user == nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
type completeAnimeRequest struct {
MalID int `json:"mal_id"`
Episode int `json:"episode"`
}
var payload completeAnimeRequest
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
if payload.MalID <= 0 || payload.Episode <= 0 {
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
animeID := int64(payload.MalID)
if _, err := h.svc.db.GetAnime(r.Context(), animeID); err != nil {
anime, fetchErr := h.jikanClient.GetAnimeByID(r.Context(), payload.MalID)
if fetchErr != nil {
log.Printf("complete anime failed to fetch anime user_id=%s mal_id=%d err=%v", user.ID, payload.MalID, fetchErr)
http.Error(w, "failed to mark anime completed", http.StatusInternalServerError)
return
}
if _, upsertErr := h.svc.db.UpsertAnime(r.Context(), database.UpsertAnimeParams{
ID: animeID,
TitleOriginal: anime.Title,
TitleEnglish: sql.NullString{String: anime.TitleEnglish, Valid: anime.TitleEnglish != ""},
TitleJapanese: sql.NullString{String: anime.TitleJapanese, Valid: anime.TitleJapanese != ""},
ImageUrl: anime.ImageURL(),
Airing: sql.NullBool{Bool: anime.Airing, Valid: true},
}); upsertErr != nil {
log.Printf("complete anime failed to upsert anime user_id=%s mal_id=%d err=%v", user.ID, payload.MalID, upsertErr)
http.Error(w, "failed to mark anime completed", http.StatusInternalServerError)
return
}
}
if _, err := h.svc.db.UpsertWatchListEntry(r.Context(), database.UpsertWatchListEntryParams{
ID: uuid.New().String(),
UserID: user.ID,
AnimeID: animeID,
Status: "completed",
CurrentEpisode: sql.NullInt64{Int64: int64(payload.Episode), Valid: true},
CurrentTimeSeconds: 0,
}); err != nil {
log.Printf("complete anime failed to upsert watchlist user_id=%s mal_id=%d err=%v", user.ID, payload.MalID, err)
http.Error(w, "failed to mark anime completed", http.StatusInternalServerError)
return
}
if err := h.svc.db.DeleteContinueWatchingEntry(r.Context(), database.DeleteContinueWatchingEntryParams{
UserID: user.ID,
AnimeID: animeID,
}); err != nil {
log.Printf("complete anime failed to delete continue entry user_id=%s mal_id=%d err=%v", user.ID, payload.MalID, err)
http.Error(w, "failed to mark anime completed", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *Handler) proxyUpstream(w http.ResponseWriter, r *http.Request, targetURL string, referer string) {
parsed, err := url.Parse(targetURL)
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {

View File

@@ -61,6 +61,7 @@ func NewRouter(cfg Config) http.Handler {
mux.HandleFunc("/watch/proxy/segment", playbackHandler.HandleProxySegment)
mux.HandleFunc("/watch/proxy/subtitle", playbackHandler.HandleProxySubtitle)
mux.HandleFunc("/api/watch-progress", playbackHandler.HandleSaveProgress)
mux.HandleFunc("/api/watch-complete", playbackHandler.HandleCompleteAnime)
// Auth Endpoints
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {

View File

@@ -605,6 +605,7 @@ const initPlayer = (): void => {
if (Number.isNaN(currentEpisode)) return
if (Number.isInteger(totalEpisodes) && totalEpisodes > 0 && currentEpisode >= totalEpisodes) {
completeAnime(currentEpisode)
return
}
@@ -615,6 +616,26 @@ const initPlayer = (): void => {
window.location.href = nextUrl
}
const completeAnime = async (episodeNumber: number): Promise<void> => {
if (!Number.isInteger(malID) || malID <= 0) return
if (!Number.isInteger(episodeNumber) || episodeNumber <= 0) return
try {
await fetch('/api/watch-complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mal_id: malID,
episode: episodeNumber,
}),
})
} catch {
return
}
}
playPause?.addEventListener('click', () => {
if (video.paused) {
video.play()