diff --git a/example/src/main/java/org/wordpress/android/fluxc/example/DomainsFragment.kt b/example/src/main/java/org/wordpress/android/fluxc/example/DomainsFragment.kt
index 7ce73ae4fc..0459ca957a 100644
--- a/example/src/main/java/org/wordpress/android/fluxc/example/DomainsFragment.kt
+++ b/example/src/main/java/org/wordpress/android/fluxc/example/DomainsFragment.kt
@@ -67,5 +67,24 @@ class DomainsFragment : StoreSelectingFragment() {
}
}
}
+
+ fetch_all_domains.setOnClickListener {
+ lifecycleScope.launch {
+ val result = store.fetchAllDomains(noWpCom = false) // fetching wpcom too for debugging purposes
+ when {
+ result.isError -> {
+ prependToLog("Error fetching all domains: ${result.error.message}")
+ }
+ else -> {
+ prependToLog("All domains count: ${result.domains?.size}")
+ val domains = result.domains
+ ?.joinToString(separator = "\n") {
+ "${it.domain} (type: ${it.type}), expiry: ${it.expiry}"
+ }
+ prependToLog("Domains:\n$domains")
+ }
+ }
+ }
+ }
}
}
diff --git a/example/src/main/res/layout/fragment_domains.xml b/example/src/main/res/layout/fragment_domains.xml
index eb8cd4adb4..665af58238 100644
--- a/example/src/main/res/layout/fragment_domains.xml
+++ b/example/src/main/res/layout/fragment_domains.xml
@@ -24,5 +24,11 @@
android:layout_height="wrap_content"
android:text="Fetch domain price" />
+
+
diff --git a/example/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClientTest.kt b/example/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClientTest.kt
index 80822f5eb0..c2e3c1f51a 100644
--- a/example/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClientTest.kt
+++ b/example/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClientTest.kt
@@ -2,6 +2,8 @@ package org.wordpress.android.fluxc.network.rest.wpcom.site
import com.android.volley.RequestQueue
import com.android.volley.VolleyError
+import com.google.gson.GsonBuilder
+import com.google.gson.reflect.TypeToken
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
@@ -16,6 +18,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.Dispatcher
+import org.wordpress.android.fluxc.UnitTestUtils
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError
import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType
@@ -526,6 +529,66 @@ class SiteRestClientTest {
assertThat(options).containsEntry("site_creation_flow", siteCreationFlow)
}
+ @Test
+ fun `when all domains are requested, then the correct response is built`() = test {
+ val allDomainsJson = "wp/all-domains/all-domains.json"
+ val json = UnitTestUtils.getStringFromResourceFile(javaClass, allDomainsJson)
+ val responseType = object : TypeToken() {}.type
+ val response = GsonBuilder().create().fromJson(json, responseType) as AllDomainsResponse
+
+ initAllDomainsResponse(data = response)
+
+ val responseModel = restClient.fetchAllDomains(noWpCom = true)
+ assert(responseModel is Success)
+ with((responseModel as Success).data) {
+ assertThat(domains).hasSize(2)
+ assertThat(domains[0].domain).isEqualTo("some.test.domain")
+ assertThat(domains[0].wpcomDomain).isFalse
+ assertThat(domains[1].domain).isEqualTo("some.test.domain 2")
+ assertThat(domains[1].wpcomDomain).isTrue
+ }
+ }
+
+ @Test
+ fun `given a network error, when all domains are requested, then return api error`() = test {
+ val error = WPComGsonNetworkError(BaseNetworkError(GenericErrorType.NETWORK_ERROR))
+ initAllDomainsResponse(error = error)
+
+ val response = restClient.fetchAllDomains(noWpCom = true)
+ assert(response is Response.Error)
+ with((response as Response.Error).error) {
+ assertThat(type).isEqualTo(GenericErrorType.NETWORK_ERROR)
+ assertThat(message).isNull()
+ }
+ }
+
+ @Test
+ fun `given timeout, when all domains are requested, then return timeout error`() = test {
+ val error = WPComGsonNetworkError(BaseNetworkError(GenericErrorType.TIMEOUT))
+ initAllDomainsResponse(error = error)
+
+ val response = restClient.fetchAllDomains(noWpCom = true)
+ assert(response is Response.Error)
+ with((response as Response.Error).error) {
+ assertThat(type).isEqualTo(GenericErrorType.TIMEOUT)
+ assertThat(message).isNull()
+ }
+ }
+
+ @Test
+ fun `given not authenticated, when all domains are requested, then retun auth required error`() = test {
+ val tokenErrorMessage = "An active access token must be used to query information about the current user."
+ val error = WPComGsonNetworkError(BaseNetworkError(GenericErrorType.NOT_AUTHENTICATED, tokenErrorMessage))
+ initAllDomainsResponse(error = error)
+
+ val response = restClient.fetchAllDomains(noWpCom = true)
+ assert(response is Response.Error)
+ with((response as Response.Error).error) {
+ assertThat(type).isEqualTo(GenericErrorType.NOT_AUTHENTICATED)
+ assertThat(message).isEqualTo(tokenErrorMessage)
+ }
+ }
+
private suspend fun initSiteResponse(
data: SiteWPComRestResponse? = null,
error: WPComGsonNetworkError? = null
@@ -561,6 +624,13 @@ class SiteRestClientTest {
return initGetResponse(SitesFeaturesRestResponse::class.java, data ?: mock(), error)
}
+ private suspend fun initAllDomainsResponse(
+ data: AllDomainsResponse? = null,
+ error: WPComGsonNetworkError? = null
+ ): Response {
+ return initGetResponse(AllDomainsResponse::class.java, data ?: mock(), error)
+ }
+
private suspend fun initGetResponse(
clazz: Class,
data: T,
diff --git a/example/src/test/java/org/wordpress/android/fluxc/store/SiteStoreTest.kt b/example/src/test/java/org/wordpress/android/fluxc/store/SiteStoreTest.kt
index 057fc94ce5..7f071a22a2 100644
--- a/example/src/test/java/org/wordpress/android/fluxc/store/SiteStoreTest.kt
+++ b/example/src/test/java/org/wordpress/android/fluxc/store/SiteStoreTest.kt
@@ -26,6 +26,7 @@ import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.PARSE_ER
import org.wordpress.android.fluxc.network.rest.wpapi.site.SiteWPAPIRestClient
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequest.WPComGsonNetworkError
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequestBuilder.Response
+import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsResponse
import org.wordpress.android.fluxc.network.rest.wpcom.site.Domain
import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainsResponse
import org.wordpress.android.fluxc.network.rest.wpcom.site.PlansResponse
@@ -38,7 +39,10 @@ import org.wordpress.android.fluxc.persistence.PostSqlUtils
import org.wordpress.android.fluxc.persistence.SiteSqlUtils
import org.wordpress.android.fluxc.persistence.domains.DomainDao
import org.wordpress.android.fluxc.persistence.jetpacksocial.JetpackSocialDao
+import org.wordpress.android.fluxc.store.SiteStore.AllDomainsError
+import org.wordpress.android.fluxc.store.SiteStore.AllDomainsErrorType
import org.wordpress.android.fluxc.store.SiteStore.FetchSitesPayload
+import org.wordpress.android.fluxc.store.SiteStore.FetchedAllDomainsPayload
import org.wordpress.android.fluxc.store.SiteStore.FetchedDomainsPayload
import org.wordpress.android.fluxc.store.SiteStore.FetchedPlansPayload
import org.wordpress.android.fluxc.store.SiteStore.FetchedPostFormatsPayload
@@ -72,8 +76,10 @@ class SiteStoreTest {
@Mock lateinit var jetpackSocialDao: JetpackSocialDao
@Mock lateinit var jetpackSocialMapper: JetpackSocialMapper
@Mock lateinit var domainsSuccessResponse: Response.Success
+ @Mock lateinit var allDomainsSuccessResponse: Response.Success
@Mock lateinit var plansSuccessResponse: Response.Success
@Mock lateinit var domainsErrorResponse: Response.Error
+ @Mock lateinit var allDomainsErrorResponse: Response.Error
@Mock lateinit var plansErrorResponse: Response.Error
private lateinit var siteStore: SiteStore
@@ -487,4 +493,30 @@ class SiteStoreTest {
assertThat(onSitePlansFetched.error.type).isEqualTo(PlansError(PlansErrorType.GENERIC_ERROR, null).type)
}
+
+ @Test
+ fun `fetchAllDomains from WPCom endpoint`() = test {
+ whenever(siteRestClient.fetchAllDomains()).thenReturn(allDomainsSuccessResponse)
+ whenever(allDomainsSuccessResponse.data).thenReturn(AllDomainsResponse(listOf()))
+
+ val onAllDomainsFetched = siteStore.fetchAllDomains()
+
+ assertThat(onAllDomainsFetched.domains).isNotNull
+ assertThat(onAllDomainsFetched.error).isNull()
+ assertThat(onAllDomainsFetched).isEqualTo(FetchedAllDomainsPayload(onAllDomainsFetched.domains))
+ }
+
+ @Test
+ fun `fetchAllDomains error from WPCom endpoint returns error`() = test {
+ val site = SiteModel()
+ site.setIsWPCom(true)
+
+ whenever(siteRestClient.fetchAllDomains()).thenReturn(allDomainsErrorResponse)
+ whenever(allDomainsErrorResponse.error).thenReturn(WPComGsonNetworkError(BaseNetworkError(NETWORK_ERROR)))
+
+ val onAllDomainsFetched = siteStore.fetchAllDomains()
+
+ val expectedErrorType = AllDomainsError(AllDomainsErrorType.GENERIC_ERROR, null).type
+ assertThat(onAllDomainsFetched.error.type).isEqualTo(expectedErrorType)
+ }
}
diff --git a/example/src/test/resources/wp/all-domains/all-domains.json b/example/src/test/resources/wp/all-domains/all-domains.json
new file mode 100644
index 0000000000..4c92fcd2d9
--- /dev/null
+++ b/example/src/test/resources/wp/all-domains/all-domains.json
@@ -0,0 +1,32 @@
+{
+ "domains": [
+ {
+ "domain": "some.test.domain",
+ "blog_id": 11111,
+ "blog_name": "some test blog",
+ "type": "mapping",
+ "is_domain_only_site": false,
+ "is_wpcom_staging_domain": false,
+ "has_registration": false,
+ "registration_date": "2009-03-26T21:20:53+00:00",
+ "expiry": "2024-03-24T00:00:00+00:00",
+ "wpcom_domain": false,
+ "current_user_is_owner": true,
+ "site_slug": "test slug"
+ },
+ {
+ "domain": "some.test.domain 2",
+ "blog_id": 22222,
+ "blog_name": "some test blog 2",
+ "type": "mapping",
+ "is_domain_only_site": false,
+ "is_wpcom_staging_domain": false,
+ "has_registration": false,
+ "registration_date": "2009-03-26T21:20:53+00:00",
+ "expiry": "2024-03-24T00:00:00+00:00",
+ "wpcom_domain": true,
+ "current_user_is_owner": false,
+ "site_slug": "test slug 2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/fluxc-processor/src/main/resources/wp-com-endpoints.txt b/fluxc-processor/src/main/resources/wp-com-endpoints.txt
index 408903117f..2c64e44a90 100644
--- a/fluxc-processor/src/main/resources/wp-com-endpoints.txt
+++ b/fluxc-processor/src/main/resources/wp-com-endpoints.txt
@@ -43,6 +43,8 @@
/read/site/$site#String/post_email_subscriptions/$action#String
/read/site/$site#String/post_email_subscriptions/update
+/all-domains
+
/domains/suggestions
/domains/supported-countries/
/domains/supported-states/$countryCode#String
diff --git a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/AllDomainsResponse.kt b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/AllDomainsResponse.kt
new file mode 100644
index 0000000000..745328abac
--- /dev/null
+++ b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/AllDomainsResponse.kt
@@ -0,0 +1,38 @@
+package org.wordpress.android.fluxc.network.rest.wpcom.site
+
+import com.google.gson.annotations.JsonAdapter
+import com.google.gson.annotations.SerializedName
+
+data class AllDomainsResponse(val domains: List)
+
+data class AllDomainsDomain(
+ @SerializedName("domain")
+ val domain: String? = null,
+ @SerializedName("blog_id")
+ val blogId: Long = 0,
+ @SerializedName("blog_name")
+ val blogName: String? = null,
+ @SerializedName("type")
+ val type: String? = null,
+ @SerializedName("is_domain_only_site")
+ @JsonAdapter(BooleanTypeAdapter::class)
+ val isDomainOnlySite: Boolean = false,
+ @SerializedName("is_wpcom_staging_domain")
+ @JsonAdapter(BooleanTypeAdapter::class)
+ val isWpcomStagingDomain: Boolean = false,
+ @SerializedName("has_registration")
+ @JsonAdapter(BooleanTypeAdapter::class)
+ val hasRegistration: Boolean = false,
+ @SerializedName("registration_date")
+ val registrationDate: String? = null,
+ @SerializedName("expiry")
+ val expiry: String? = null,
+ @SerializedName("wpcom_domain")
+ @JsonAdapter(BooleanTypeAdapter::class)
+ val wpcomDomain: Boolean = false,
+ @SerializedName("current_user_is_owner")
+ @JsonAdapter(BooleanTypeAdapter::class)
+ val currentUserIsOwner: Boolean = false,
+ @SerializedName("site_slug")
+ val siteSlug: String? = null,
+)
diff --git a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/BooleanTypeAdapter.kt b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/BooleanTypeAdapter.kt
new file mode 100644
index 0000000000..4a71d07772
--- /dev/null
+++ b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/BooleanTypeAdapter.kt
@@ -0,0 +1,25 @@
+package org.wordpress.android.fluxc.network.rest.wpcom.site
+
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonDeserializer
+import com.google.gson.JsonElement
+import com.google.gson.JsonParseException
+import java.lang.reflect.Type
+import java.util.Locale
+
+internal class BooleanTypeAdapter : JsonDeserializer {
+ @Suppress("VariableNaming") private val TRUE_STRINGS: Set = HashSet(listOf("true", "1", "yes"))
+
+ @Throws(JsonParseException::class)
+ override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean {
+ val jsonPrimitive = json.asJsonPrimitive
+ return when {
+ jsonPrimitive.isBoolean -> jsonPrimitive.asBoolean
+ jsonPrimitive.isNumber -> jsonPrimitive.asNumber.toInt() == 1
+ jsonPrimitive.isString -> TRUE_STRINGS.contains(jsonPrimitive.asString.toLowerCase(
+ Locale.getDefault()
+ ))
+ else -> false
+ }
+ }
+}
diff --git a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/DomainsResponse.kt b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/DomainsResponse.kt
index 1c2df73d01..88bb727a5b 100644
--- a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/DomainsResponse.kt
+++ b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/DomainsResponse.kt
@@ -1,13 +1,8 @@
package org.wordpress.android.fluxc.network.rest.wpcom.site
-import com.google.gson.JsonDeserializationContext
-import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
-import com.google.gson.JsonParseException
import com.google.gson.annotations.JsonAdapter
import com.google.gson.annotations.SerializedName
-import java.lang.reflect.Type
-import java.util.Locale
data class DomainsResponse(val domains: List)
@@ -155,17 +150,3 @@ data class TitanMailSubscription(
val status: String? = null
)
-internal class BooleanTypeAdapter : JsonDeserializer {
- @Suppress("VariableNaming") private val TRUE_STRINGS: Set = HashSet(listOf("true", "1", "yes"))
-
- @Throws(JsonParseException::class)
- override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean {
- val jsonPrimitive = json.asJsonPrimitive
- return when {
- jsonPrimitive.isBoolean -> jsonPrimitive.asBoolean
- jsonPrimitive.isNumber -> jsonPrimitive.asNumber.toInt() == 1
- jsonPrimitive.isString -> TRUE_STRINGS.contains(jsonPrimitive.asString.toLowerCase(Locale.getDefault()))
- else -> false
- }
- }
-}
diff --git a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt
index 8d440f4208..06636eeed0 100644
--- a/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt
+++ b/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/site/SiteRestClient.kt
@@ -837,6 +837,11 @@ class SiteRestClient @Inject constructor(
add(request)
}
+ suspend fun fetchAllDomains(noWpCom: Boolean = true): Response {
+ val url = WPCOMREST.all_domains.urlV1_1
+ val params = mapOf("no_wpcom" to noWpCom.toString())
+ return wpComGsonRequestBuilder.syncGetRequest(this, url, params, AllDomainsResponse::class.java)
+ }
suspend fun fetchSiteDomains(site: SiteModel): Response {
val url = WPCOMREST.sites.site(site.siteId).domains.urlV1_1
return wpComGsonRequestBuilder.syncGetRequest(this, url, mapOf(), DomainsResponse::class.java)
diff --git a/fluxc/src/main/java/org/wordpress/android/fluxc/store/SiteStore.kt b/fluxc/src/main/java/org/wordpress/android/fluxc/store/SiteStore.kt
index d058b0043c..8e8c26764d 100644
--- a/fluxc/src/main/java/org/wordpress/android/fluxc/store/SiteStore.kt
+++ b/fluxc/src/main/java/org/wordpress/android/fluxc/store/SiteStore.kt
@@ -87,6 +87,7 @@ import org.wordpress.android.fluxc.network.rest.wpapi.site.SiteWPAPIRestClient
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequest.WPComGsonNetworkError
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequestBuilder.Response.Error
import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequestBuilder.Response.Success
+import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain
import org.wordpress.android.fluxc.network.rest.wpcom.site.Domain
import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainPriceResponse
import org.wordpress.android.fluxc.network.rest.wpcom.site.DomainSuggestionResponse
@@ -490,6 +491,11 @@ open class SiteStore @Inject constructor(
}
}
+ data class AllDomainsError @JvmOverloads constructor(
+ @JvmField val type: AllDomainsErrorType,
+ @JvmField val message: String? = null,
+ ) : OnChangedError
+
data class SiteError @JvmOverloads constructor(
@JvmField val type: SiteErrorType,
@JvmField val message: String? = null,
@@ -757,6 +763,14 @@ open class SiteStore @Inject constructor(
}
}
+ data class FetchedAllDomainsPayload(
+ @JvmField val domains: List? = null
+ ) : Payload() {
+ constructor(error: AllDomainsError) : this() {
+ this.error = error
+ }
+ }
+
data class FetchedDomainsPayload(
@JvmField val site: SiteModel,
@JvmField val domains: List? = null
@@ -886,6 +900,10 @@ open class SiteStore @Inject constructor(
INVALID_SITE, UNKNOWN_SITE, DUPLICATE_SITE, INVALID_RESPONSE, UNAUTHORIZED, NOT_AUTHENTICATED, GENERIC_ERROR
}
+ enum class AllDomainsErrorType {
+ UNAUTHORIZED, GENERIC_ERROR
+ }
+
enum class SuggestDomainErrorType {
EMPTY_RESULTS, EMPTY_QUERY, INVALID_MINIMUM_QUANTITY, INVALID_MAXIMUM_QUANTITY, INVALID_QUERY, GENERIC_ERROR;
@@ -2080,6 +2098,24 @@ open class SiteStore @Inject constructor(
emitChange(event)
}
+ suspend fun fetchAllDomains(noWpCom: Boolean = true): FetchedAllDomainsPayload =
+ coroutineEngine.withDefaultContext(T.API, this, "Fetch all domains") {
+ return@withDefaultContext when (val response =
+ siteRestClient.fetchAllDomains(noWpCom)) {
+ is Success -> {
+ val domains = response.data.domains
+ FetchedAllDomainsPayload(domains)
+ }
+ is Error -> {
+ val errorType = when (response.error.apiError) {
+ "authorization_required" -> AllDomainsErrorType.UNAUTHORIZED
+ else -> AllDomainsErrorType.GENERIC_ERROR
+ }
+ val domainsError = AllDomainsError(errorType, response.error.message)
+ FetchedAllDomainsPayload(domainsError)
+ }
+ }
+ }
suspend fun fetchSiteDomains(siteModel: SiteModel): FetchedDomainsPayload =
coroutineEngine.withDefaultContext(T.API, this, "Fetch site domains") {
return@withDefaultContext when (val response =