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.
This commit is contained in:
2026-09-01 19:49:47 -04:00
parent a16c804692
commit 5a14863a8f
3 changed files with 144 additions and 77 deletions
+64 -7
View File
@@ -558,7 +558,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (An
return post, nil return post, nil
} }
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime { func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) (AniListGetSingleAnime, error) {
body := struct { body := struct {
Query string `json:"query"` Query string `json:"query"`
Variables AniListUpdateVariables `json:"variables"` Variables AniListUpdateVariables `json:"variables"`
@@ -584,6 +584,10 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi
startedAt: $startedAt startedAt: $startedAt
completedAt: $completedAt completedAt: $completedAt
) { ) {
id
mediaId
userId
media {
id id
idMal idMal
title { title {
@@ -661,7 +665,6 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi
} }
isAdult isAdult
} }
status status
startedAt { startedAt {
year year
@@ -700,22 +703,51 @@ func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSi
Variables: updateBody, 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 var returnedJson AniListUpdateReturn
err := json.Unmarshal(returnedBody, &returnedJson) err := json.Unmarshal(returnedBody, &returnedJson)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) 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 var post AniListGetSingleAnime
post.Data.MediaList = returnedJson.Data.SaveMediaListEntry 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 { type Variables = struct {
Id int `json:"id"` 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 var post DeleteAniListReturn
err := json.Unmarshal(returnedBody, &post) err := json.Unmarshal(returnedBody, &post)
if err != nil { if err != nil {
log.Printf("Failed at unmarshal, %s\n", err) 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( func (a *App) AniListBrowse(
@@ -4,6 +4,7 @@
aniListLoggedIn, aniListLoggedIn,
malAnime, malAnime,
malLoggedIn, malLoggedIn,
setApiError,
simklAnime, simklAnime,
simklLoggedIn, simklLoggedIn,
watchlistNeedsRefresh, watchlistNeedsRefresh,
@@ -207,6 +208,15 @@
completedAt: convertDateToAniList(completedAtDate), completedAt: convertDateToAniList(completedAtDate),
}; };
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => { 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 = value.data.MediaList.media.tags =
currentAniListAnime.data.MediaList.media.tags; currentAniListAnime.data.MediaList.media.tags;
value.data.MediaList.media.genres = value.data.MediaList.media.genres =
+1 -1
View File
@@ -12,6 +12,6 @@
}, },
"info": { "info": {
"productName": "AniTrack", "productName": "AniTrack",
"productVersion": "1.5.5" "productVersion": "1.6.1"
} }
} }