Skip to content

Commit bca65e9

Browse files
Update to the latest OpenAPI spec (#413)
* Update the openAPI spec and related code * Adjust QE code to only select an Avatar when none is set * Make hash and selecteAvatar optional when uploading avatar * Rely on backend default for selecting the avatar * Generate gravatar.api file * Update gravatar/src/main/java/com/gravatar/services/AvatarService.kt Co-authored-by: Héctor Abraham Morillo Prieto <hector.abraham.morillo.prieto@automattic.com> * Add test to verify null was passed to upload API call --------- Co-authored-by: Héctor Abraham Morillo Prieto <hector.abraham.morillo.prieto@automattic.com>
1 parent d7ef199 commit bca65e9

10 files changed

Lines changed: 182 additions & 87 deletions

File tree

config/detekt/detekt.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ comments:
7777
searchProtectedProperty: false
7878
OutdatedDocumentation:
7979
active: true
80-
excludes: [ '**/test/**', '**/androidTest/**', '**/demoapp/**', '**/api/models/**', '**/restapi/models/**' ]
80+
excludes: [ '**/test/**', '**/androidTest/**', '**/demoapp/**', '**/restapi/models/**', '**/restapi/apis/**' ]
8181
matchTypeParameters: true
8282
matchDeclarationsOrder: true
8383
allowParamOnConstructorProperties: false

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/data/repository/AvatarRepository.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,19 @@ internal class AvatarRepository(
4949
} ?: GravatarResult.Failure(QuickEditorError.TokenNotFound)
5050
}
5151

52-
suspend fun uploadAvatar(email: Email, avatarUri: Uri): GravatarResult<Avatar, QuickEditorError> = withContext(
53-
dispatcher,
54-
) {
55-
val token = tokenStorage.getToken(email.hash().toString())
56-
token?.let {
57-
when (val result = avatarService.uploadCatching(avatarUri.toFile(), token)) {
58-
is GravatarResult.Success -> GravatarResult.Success(result.value)
59-
is GravatarResult.Failure -> GravatarResult.Failure(QuickEditorError.Request(result.error))
60-
}
61-
} ?: GravatarResult.Failure(QuickEditorError.TokenNotFound)
62-
}
52+
suspend fun uploadAvatar(email: Email, avatarUri: Uri): GravatarResult<Avatar, QuickEditorError> =
53+
withContext(dispatcher) {
54+
val hash = email.hash()
55+
val token = tokenStorage.getToken(hash.toString())
56+
token?.let {
57+
when (
58+
val result = avatarService.uploadCatching(avatarUri.toFile(), token, hash)
59+
) {
60+
is GravatarResult.Success -> GravatarResult.Success(result.value)
61+
is GravatarResult.Failure -> GravatarResult.Failure(QuickEditorError.Request(result.error))
62+
}
63+
} ?: GravatarResult.Failure(QuickEditorError.TokenNotFound)
64+
}
6365
}
6466

6567
internal data class EmailAvatars(

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/avatarpicker/AvatarPickerViewModel.kt

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -146,34 +146,33 @@ internal class AvatarPickerViewModel(
146146
when (val result = avatarRepository.uploadAvatar(email, uri)) {
147147
is GravatarResult.Success -> {
148148
fileUtils.deleteFile(uri)
149-
val isAutoSelected = _uiState.value.emailAvatars?.selectedAvatarId == null
150-
if (isAutoSelected) {
151-
fetchAvatars(showLoading = false, scrollToSelected = true)
152-
_uiState.update { currentState ->
153-
currentState.copy(
154-
uploadingAvatar = null,
155-
avatarUpdates = currentState.avatarUpdates.inc(),
156-
)
157-
}
158-
if (_uiState.value.emailAvatars?.selectedAvatarId != null) {
159-
_actions.send(AvatarPickerAction.AvatarSelected)
160-
}
161-
} else {
162-
_uiState.update { currentState ->
163-
val avatar = result.value
164-
currentState.copy(
165-
uploadingAvatar = null,
166-
emailAvatars = currentState.emailAvatars?.copy(
167-
avatars = buildList {
168-
add(avatar)
169-
addAll(
170-
currentState.emailAvatars.avatars.filter { it.imageId != avatar.imageId },
171-
)
172-
},
173-
),
174-
scrollToIndex = null,
175-
)
176-
}
149+
val avatar = result.value
150+
if (avatar.selected == true) {
151+
_actions.send(AvatarPickerAction.AvatarSelected)
152+
}
153+
_uiState.update { currentState ->
154+
currentState.copy(
155+
uploadingAvatar = null,
156+
emailAvatars = currentState.emailAvatars?.copy(
157+
avatars = buildList {
158+
add(avatar)
159+
addAll(
160+
currentState.emailAvatars.avatars.filter { it.imageId != avatar.imageId },
161+
)
162+
},
163+
selectedAvatarId = if (avatar.selected == true) {
164+
avatar.imageId
165+
} else {
166+
currentState.emailAvatars.selectedAvatarId
167+
},
168+
),
169+
scrollToIndex = null,
170+
avatarUpdates = if (avatar.selected == true) {
171+
currentState.avatarUpdates.inc()
172+
} else {
173+
currentState.avatarUpdates
174+
},
175+
)
177176
}
178177
}
179178

gravatar-quickeditor/src/test/java/com/gravatar/quickeditor/data/repository/AvatarRepositoryTest.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ class AvatarRepositoryTest {
121121
fun `given token not stored when avatar upload then Failure result`() = runTest {
122122
val uri = mockk<Uri>()
123123
coEvery { tokenStorage.getToken(any()) } returns null
124-
coEvery { avatarService.uploadCatching(any(), any()) } returns GravatarResult.Success(createAvatar("1"))
124+
coEvery {
125+
avatarService.uploadCatching(any(), any(), any(), any())
126+
} returns GravatarResult.Success(createAvatar("1"))
125127

126128
val result = avatarRepository.uploadAvatar(email, uri)
127129

@@ -137,7 +139,7 @@ class AvatarRepositoryTest {
137139
every { toFile() } returns file
138140
}
139141
coEvery { tokenStorage.getToken(any()) } returns "token"
140-
coEvery { avatarService.uploadCatching(any(), any()) } returns GravatarResult.Success(avatar)
142+
coEvery { avatarService.uploadCatching(any(), any(), any(), any()) } returns GravatarResult.Success(avatar)
141143

142144
val result = avatarRepository.uploadAvatar(email, uri)
143145

@@ -152,7 +154,9 @@ class AvatarRepositoryTest {
152154
every { toFile() } returns file
153155
}
154156
coEvery { tokenStorage.getToken(any()) } returns "token"
155-
coEvery { avatarService.uploadCatching(any(), any()) } returns GravatarResult.Failure(ErrorType.Server)
157+
coEvery {
158+
avatarService.uploadCatching(any(), any(), any(), any())
159+
} returns GravatarResult.Failure(ErrorType.Server)
156160

157161
val result = avatarRepository.uploadAvatar(email, uri)
158162

gravatar-quickeditor/src/test/java/com/gravatar/quickeditor/ui/avatarpicker/AvatarPickerViewModelTest.kt

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -719,10 +719,9 @@ class AvatarPickerViewModelTest {
719719
expectMostRecentItem()
720720

721721
val emailAvatarsUpdated = emailAvatars.copy(
722-
avatars = listOf(createAvatar("1"), createAvatar("3")),
722+
avatars = listOf(createAvatar("1", isSelected = true), createAvatar("3", isSelected = false)),
723723
selectedAvatarId = "1",
724724
)
725-
coEvery { avatarRepository.getAvatars(any()) } returns GravatarResult.Success(emailAvatarsUpdated)
726725
viewModel.onEvent(AvatarPickerEvent.ImageCropped(uriOne))
727726

728727
// State before upload starts
@@ -741,25 +740,6 @@ class AvatarPickerViewModelTest {
741740
avatarPickerUiState,
742741
awaitItem(),
743742
)
744-
745-
// State produced by fetchAvatars within Upload.Success
746-
avatarPickerUiState = AvatarPickerUiState(
747-
email = email,
748-
emailAvatars = emailAvatarsUpdated,
749-
error = null,
750-
profile = ComponentState.Loaded(profile),
751-
selectingAvatarId = null,
752-
uploadingAvatar = uriOne,
753-
scrollToIndex = 0,
754-
avatarPickerContentLayout = avatarPickerContentLayout,
755-
avatarUpdates = 0,
756-
)
757-
758-
assertEquals(
759-
avatarPickerUiState,
760-
awaitItem(),
761-
)
762-
763743
// State produced before finishing uploadAvatar, just after fetchAvatars has finished
764744
avatarPickerUiState = AvatarPickerUiState(
765745
email = email,
@@ -768,11 +748,10 @@ class AvatarPickerViewModelTest {
768748
profile = ComponentState.Loaded(profile),
769749
selectingAvatarId = null,
770750
uploadingAvatar = null,
771-
scrollToIndex = 0,
751+
scrollToIndex = null,
772752
avatarPickerContentLayout = avatarPickerContentLayout,
773753
avatarUpdates = 1,
774754
)
775-
776755
assertEquals(
777756
avatarPickerUiState,
778757
awaitItem(),

gravatar/api/gravatar.api

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,8 +647,10 @@ public final class com/gravatar/services/AvatarService {
647647
public final fun retrieveCatching (Ljava/lang/String;Lcom/gravatar/types/Hash;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
648648
public final fun setAvatar (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
649649
public final fun setAvatarCatching (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
650-
public final fun upload (Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
651-
public final fun uploadCatching (Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
650+
public final fun upload (Ljava/io/File;Ljava/lang/String;Lcom/gravatar/types/Hash;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
651+
public static synthetic fun upload$default (Lcom/gravatar/services/AvatarService;Ljava/io/File;Ljava/lang/String;Lcom/gravatar/types/Hash;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
652+
public final fun uploadCatching (Ljava/io/File;Ljava/lang/String;Lcom/gravatar/types/Hash;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
653+
public static synthetic fun uploadCatching$default (Lcom/gravatar/services/AvatarService;Ljava/io/File;Ljava/lang/String;Lcom/gravatar/types/Hash;Ljava/lang/Boolean;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
652654
}
653655

654656
public abstract interface class com/gravatar/services/ErrorType {

gravatar/openapi/api-gravatar.json

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@
610610
"implicit": {
611611
"authorizationUrl": "https://public-api.wordpress.com/oauth2/authorize",
612612
"tokenUrl": "https://public-api.wordpress.com/oauth2/token",
613-
"scopes": {}
613+
"scopes": {
614+
}
614615
}
615616
},
616617
"description": "WordPress OAuth token to authenticate the request."
@@ -818,10 +819,10 @@
818819
{
819820
"name": "selected_email_hash",
820821
"in": "query",
821-
"description": "The sha256 hash of the email address used to determine which avatar is selected. The 'selected' attribute in the avatar list will be set to 'true' for the avatar associated with this email.",
822+
"description": "The SHA256 hash of the email address used to determine which avatar is selected. The 'selected' attribute in the avatar list will be set to 'true' for the avatar associated with this email.",
822823
"schema": {
823824
"type": "string",
824-
"default": null
825+
"default": ""
825826
}
826827
}
827828
],
@@ -861,6 +862,30 @@
861862
"oauth": []
862863
}
863864
],
865+
"parameters": [
866+
{
867+
"name": "selected_email_hash",
868+
"in": "query",
869+
"description": "The SHA256 hash of email. If provided, the uploaded image will be selected as the avatar for this email.",
870+
"required": false,
871+
"schema": {
872+
"type": "string"
873+
}
874+
},
875+
{
876+
"name": "select_avatar",
877+
"in": "query",
878+
"description": "Determines if the uploaded image should be set as the avatar for the email. If not passed, the image is only selected as the email's avatar if no previous avatar has been set. Accepts '1'/'true' to always set the avatar or '0'/'false' to never set the avatar.",
879+
"required": false,
880+
"schema": {
881+
"type": [
882+
"boolean",
883+
"null"
884+
],
885+
"default": null
886+
}
887+
}
888+
],
864889
"requestBody": {
865890
"required": true,
866891
"content": {

gravatar/src/main/java/com/gravatar/restapi/apis/AvatarsApi.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ internal interface AvatarsApi {
2828
* - 401: Not Authorized
2929
* - 403: Insufficient Scope
3030
*
31-
* @param selectedEmailHash The sha256 hash of the email address used to determine which avatar is selected. The &#39;selected&#39; attribute in the avatar list will be set to &#39;true&#39; for the avatar associated with this email. (optional, default to "null")
31+
* @param selectedEmailHash The SHA256 hash of the email address used to determine which avatar is selected. The &#39;selected&#39; attribute in the avatar list will be set to &#39;true&#39; for the avatar associated with this email. (optional, default to "")
3232
* @return [kotlin.collections.List<Avatar>]
3333
*/
3434
@GET("me/avatars")
3535
suspend fun getAvatars(
36-
@Query("selected_email_hash") selectedEmailHash: kotlin.String? = "null",
36+
@Query("selected_email_hash") selectedEmailHash: kotlin.String? = "",
3737
): Response<kotlin.collections.List<Avatar>>
3838

3939
/**
@@ -64,11 +64,15 @@ internal interface AvatarsApi {
6464
* - 403: Insufficient Scope
6565
*
6666
* @param `data` The avatar image file
67+
* @param selectedEmailHash The SHA256 hash of email. If provided, the uploaded image will be selected as the avatar for this email. (optional)
68+
* @param selectAvatar Determines if the uploaded image should be set as the avatar for the email. If not passed, the image is only selected as the email&#39;s avatar if no previous avatar has been set. Accepts &#39;1&#39;/&#39;true&#39; to always set the avatar or &#39;0&#39;/&#39;false&#39; to never set the avatar. (optional, default to null)
6769
* @return [Avatar]
6870
*/
6971
@Multipart
7072
@POST("me/avatars")
7173
suspend fun uploadAvatar(
7274
@Part `data`: MultipartBody.Part,
75+
@Query("selected_email_hash") selectedEmailHash: kotlin.String? = null,
76+
@Query("select_avatar") selectAvatar: kotlin.Boolean? = null,
7377
): Response<Avatar>
7478
}

gravatar/src/main/java/com/gravatar/services/AvatarService.kt

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,28 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
2828
*
2929
* @param file The image file to upload
3030
* @param oauthToken The OAuth token to use for authentication
31+
* @param hash The hash of the email to associate the avatar with.
32+
* If null the primary email of the account will be used.
33+
* @param selectAvatar Determines if the uploaded image should be set automatically as the avatar
34+
* for the given hash. If null, the avatar will be selected only if no other avatar is set.
3135
*/
32-
public suspend fun upload(file: File, oauthToken: String): Avatar = runThrowingExceptionRequest {
36+
public suspend fun upload(
37+
file: File,
38+
oauthToken: String,
39+
hash: Hash? = null,
40+
selectAvatar: Boolean? = null,
41+
): Avatar = runThrowingExceptionRequest {
3342
withContext(GravatarSdkDI.dispatcherIO) {
3443
val service = instance.getGravatarV3Service(okHttpClient, oauthToken)
3544

3645
val filePart =
3746
MultipartBody.Part.createFormData("image", file.name, file.asRequestBody())
3847

39-
val response = service.uploadAvatar(filePart)
48+
val response = service.uploadAvatar(
49+
data = filePart,
50+
selectedEmailHash = hash?.toString(),
51+
selectAvatar = selectAvatar,
52+
)
4053

4154
if (response.isSuccessful && response.body() != null) {
4255
response.body()!!
@@ -59,11 +72,19 @@ public class AvatarService(private val okHttpClient: OkHttpClient? = null) {
5972
* @param file The image file to upload
6073
* @param oauthToken The OAuth token to use for authentication
6174
* @return The result of the operation
75+
* @param hash The hash of the email to associate the avatar with.
76+
* If null the primary email of the account will be used.
77+
* @param selectAvatar Determines if the uploaded image should be set automatically as the avatar
78+
* for the given hash. If null, the avatar will be selected only if no other avatar is set.
6279
*/
63-
public suspend fun uploadCatching(file: File, oauthToken: String): GravatarResult<Avatar, ErrorType> =
64-
runCatchingRequest {
65-
upload(file, oauthToken)
66-
}
80+
public suspend fun uploadCatching(
81+
file: File,
82+
oauthToken: String,
83+
hash: Hash? = null,
84+
selectAvatar: Boolean? = null,
85+
): GravatarResult<Avatar, ErrorType> = runCatchingRequest {
86+
upload(file, oauthToken, hash, selectAvatar)
87+
}
6788

6889
/**
6990
* Retrieves a list of available avatars for the authenticated user.

0 commit comments

Comments
 (0)