From 5a14863a8fec177b163ebf5cc6c95eb6b53234a3 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Tue, 1 Sep 2026 19:49:47 -0400 Subject: [PATCH] fix(anilist): repair SaveMediaListEntry mutation and surface update failures The AniListUpdateEntry mutation was mangled in 54c109a when the standardized media field block was pasted in without the media { } wrapper: media-level fields (idMal, title, ...) sat directly on SaveMediaListEntry (which returns MediaList), the MediaList fields (status, startedAt, ..., user) ended up at the Mutation root, and a stray closing brace made the document a guaranteed 400. Because the response status was discarded, the frontend received a zeroed AniListGetSingleAnime and blanked the anime page after every submit, making AniList appear logged out (and the change was never saved). - Restore the mutation to match the tested bruno request: id/mediaId/ userId, standard media block inside media { }, MediaList fields inside the selection, balanced braces. - AniListUpdateEntry and AniListDeleteEntry now return an error on 403/non-200/unparseable responses (mirroring GetAniListUserWatchingList) instead of silently returning zero values. - Anime.svelte guards against replacing the page data with an empty response and raises the API error modal instead. Bump productVersion to 1.6.1. --- AniListFunctions.go | 209 +++++++++++++-------- frontend/src/helperComponents/Anime.svelte | 10 + wails.json | 2 +- 3 files changed, 144 insertions(+), 77 deletions(-) diff --git a/AniListFunctions.go b/AniListFunctions.go index c6cca03..cf4441d 100644 --- a/AniListFunctions.go +++ b/AniListFunctions.go @@ -558,7 +558,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (An return post, nil } -func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime { +func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) (AniListGetSingleAnime, error) { body := struct { Query string `json:"query"` Variables AniListUpdateVariables `json:"variables"` @@ -584,84 +584,87 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi startedAt: $startedAt completedAt: $completedAt ) { - id - idMal - title { - userPreferred - romaji - english - native - } - description - coverImage { - extraLarge - large - medium - color - } - bannerImage - format - season - seasonYear - status - episodes - duration - countryOfOrigin - source - synonyms - averageScore - meanScore - popularity - trending - favourites - isFavourite - relations { - nodes { + id + mediaId + userId + media { id + idMal title { userPreferred romaji english native } + description + coverImage { + extraLarge + large + medium + color + } + bannerImage + format + season + seasonYear + status + episodes + duration + countryOfOrigin + source + synonyms + averageScore + meanScore + popularity + trending + favourites + isFavourite + relations { + nodes { + id + title { + userPreferred + romaji + english + native + } + } + } + startDate { + year + month + day + } + endDate { + year + month + day + } + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + airingSchedule { + nodes { + id + airingAt + timeUntilAiring + episode + mediaId + } + } + genres + tags { + id + name + description + rank + isMediaSpoiler + isAdult + } + isAdult } - } - startDate { - year - month - day - } - endDate { - year - month - day - } - nextAiringEpisode { - airingAt - timeUntilAiring - episode - } - airingSchedule { - nodes { - id - airingAt - timeUntilAiring - episode - mediaId - } - } - genres - tags { - id - name - description - rank - isMediaSpoiler - isAdult - } - isAdult -} - status startedAt { year @@ -700,22 +703,51 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi Variables: updateBody, } - returnedBody, _ := AniListQuery(body, true) + returnedBody, status := AniListQuery(body, true) + + var badPost struct { + Errors []struct { + Message string `json:"message"` + Status int `json:"status"` + Locations []struct { + Line int `json:"line"` + Column int `json:"column"` + } `json:"locations"` + } `json:"errors"` + Data any `json:"data"` + } + + if status == "403 Forbidden" { + err := json.Unmarshal(returnedBody, &badPost) + if err != nil { + log.Printf("Failed at unmarshal, %s\n", err) + return AniListGetSingleAnime{}, fmt.Errorf("API authentication error") + } + return AniListGetSingleAnime{}, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message) + } + if status != "200 OK" { + return AniListGetSingleAnime{}, fmt.Errorf("API request failed with status: %s", status) + } var returnedJson AniListUpdateReturn err := json.Unmarshal(returnedBody, &returnedJson) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) + return AniListGetSingleAnime{}, fmt.Errorf("failed to parse AniList update response") + } + + if returnedJson.Data.SaveMediaListEntry.MediaID == 0 { + return AniListGetSingleAnime{}, fmt.Errorf("AniList returned no saved entry") } var post AniListGetSingleAnime post.Data.MediaList = returnedJson.Data.SaveMediaListEntry - return post + return post, nil } -func (a *App) AniListDeleteEntry(mediaListId int) DeleteAniListReturn { +func (a *App) AniListDeleteEntry(mediaListId int) (DeleteAniListReturn, error) { type Variables = struct { Id int `json:"id"` } @@ -740,15 +772,40 @@ func (a *App) AniListDeleteEntry(mediaListId int) DeleteAniListReturn { }, } - returnedBody, _ := AniListQuery(body, true) + returnedBody, status := AniListQuery(body, true) + + var badPost struct { + Errors []struct { + Message string `json:"message"` + Status int `json:"status"` + Locations []struct { + Line int `json:"line"` + Column int `json:"column"` + } `json:"locations"` + } `json:"errors"` + Data any `json:"data"` + } + + if status == "403 Forbidden" { + err := json.Unmarshal(returnedBody, &badPost) + if err != nil { + log.Printf("Failed at unmarshal, %s\n", err) + return DeleteAniListReturn{}, fmt.Errorf("API authentication error") + } + return DeleteAniListReturn{}, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message) + } + if status != "200 OK" { + return DeleteAniListReturn{}, fmt.Errorf("API request failed with status: %s", status) + } var post DeleteAniListReturn err := json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) + return DeleteAniListReturn{}, fmt.Errorf("failed to parse AniList delete response") } - return post + return post, nil } func (a *App) AniListBrowse( diff --git a/frontend/src/helperComponents/Anime.svelte b/frontend/src/helperComponents/Anime.svelte index bc2c6da..db3c67a 100644 --- a/frontend/src/helperComponents/Anime.svelte +++ b/frontend/src/helperComponents/Anime.svelte @@ -4,6 +4,7 @@ aniListLoggedIn, malAnime, malLoggedIn, + setApiError, simklAnime, simklLoggedIn, watchlistNeedsRefresh, @@ -207,6 +208,15 @@ completedAt: convertDateToAniList(completedAtDate), }; await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => { + if (!value.data?.MediaList || value.data.MediaList.mediaId === 0) { + setApiError( + "anilist", + "AniList update failed: no saved entry was returned", + undefined, + true, + ); + return; + } value.data.MediaList.media.tags = currentAniListAnime.data.MediaList.media.tags; value.data.MediaList.media.genres = diff --git a/wails.json b/wails.json index 1374954..8ad828a 100644 --- a/wails.json +++ b/wails.json @@ -12,6 +12,6 @@ }, "info": { "productName": "AniTrack", - "productVersion": "1.5.5" + "productVersion": "1.6.1" } }