What? Why?
#49 added support for JPEG deck covers loaded from local files.
However, it would be nice to
- be able to set covers from existing image URLs (as suggested here by @floscha),
- handle other formats (e.g. PNG).
In order to do this, the coverImageUrl field would have to be supported.
How?
Below is an extract of requests/responses with the fields relevant to covers, when manually interacting with decks on https://tinycards.duolingo.com.
Observations
- Invariant 1.: before a custom cover is set,
coverImageUrl is always null.
- Invariant 2.: in deck updates, even for deck cover updates,
coverImageUrl is always sent back, with the latest value received from the server-side, i.e. null if no custom cover, or the last known URL to server-side otherwise.
- Invariant 3.: responses from
POSTs/PATCHs requests and subsequent GET requests are always equivalent w.r.t. to imageUrl and coverImageUrl.
1. Deck creation w/o cover:
POST https://tinycards.duolingo.com/api/1/decks
- no
imageFile field
- no
imageUrl field
- no
imageCoverUrl field
Response:
{
"coverImageUrl": null,
"imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096"
}
2. Subsequent deck loading:
GET https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": null,
"imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096",
}
3. Deck cards update:
PATCH https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": null
}
Response:
{
"coverImageUrl": null,
"imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096"
}
4. Subsequent deck loading:
GET https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": null,
"imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096",
}
5. Deck cover update from local file:
PATCH https://tinycards.duolingo.com/api/1/decks/{deckID}
------<separator>
Content-Disposition: form-data; name="imageFile"; filename="cover.jpg"
Content-Type: image/jpeg
[...bytes...]
------<separator>
Content-Disposition: form-data; name="coverImageUrl"
null
Response:
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353"
}
6. Subsequent deck loading:
GET https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353"
}
7. Deck cards update
PATCH https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353"
}
Response:
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353"
}
8. Subsequent deck loading:
GET https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353"
}
9. Deck cover update from Tinycards' image library:
PATCH https://tinycards.duolingo.com/api/1/decks/{deckID}
------<separator>
Content-Disposition: form-data; name="imageAttribution"
<source URL of the image>
------<separator>
Content-Disposition: form-data; name="imageFile"; filename="cover.jpg"
Content-Type: image/png
[...bytes...]
------<separator>
Content-Disposition: form-data; name="coverImageUrl"
https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353
Response:
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7"
}
10. Subsequent deck loading:
GET https://tinycards.duolingo.com/api/1/decks/{deckID}
{
"coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7",
"imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7"
}
What? Why?
#49 added support for JPEG deck covers loaded from local files.
However, it would be nice to
In order to do this, the
coverImageUrlfield would have to be supported.How?
Below is an extract of requests/responses with the fields relevant to covers, when manually interacting with decks on https://tinycards.duolingo.com.
Observations
coverImageUrlis alwaysnull.coverImageUrlis always sent back, with the latest value received from the server-side, i.e.nullif no custom cover, or the last known URL to server-side otherwise.POSTs/PATCHs requests and subsequentGETrequests are always equivalent w.r.t. toimageUrlandcoverImageUrl.1. Deck creation w/o cover:
POSThttps://tinycards.duolingo.com/api/1/decksimageFilefieldimageUrlfieldimageCoverUrlfieldResponse:
{ "coverImageUrl": null, "imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096" }2. Subsequent deck loading:
GEThttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": null, "imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096", }3. Deck cards update:
PATCHhttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": null }Response:
{ "coverImageUrl": null, "imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096" }4. Subsequent deck loading:
GEThttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": null, "imageUrl": "https://s3.amazonaws.com/tinycards/image/16cb6cbcb086ae0f622d1cfb7553a096", }5. Deck cover update from local file:
PATCHhttps://tinycards.duolingo.com/api/1/decks/{deckID}Response:
{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353" }6. Subsequent deck loading:
GEThttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353" }7. Deck cards update
PATCHhttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353" }Response:
{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353" }8. Subsequent deck loading:
GEThttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/9f6a54aef25a5184620e5762c8430353" }9. Deck cover update from Tinycards' image library:
PATCHhttps://tinycards.duolingo.com/api/1/decks/{deckID}Response:
{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7" }10. Subsequent deck loading:
GEThttps://tinycards.duolingo.com/api/1/decks/{deckID}{ "coverImageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7", "imageUrl": "https://d9np3dj86nsu2.cloudfront.net/image/ada59f16c4e4d8b09f7eee9104ce6bc7" }