@@ -1619,16 +1619,15 @@ open class SiteStore @Inject constructor(
16191619 // updateSite's full-row write skips WP_API_REST_URL and the credential columns, and this fresh
16201620 // site has no local id (it's matched by URL), so persist them via the URL-keyed writers.
16211621 if (! siteModel.isError) {
1622- val username = siteModel.apiRestUsernamePlain
1623- val password = siteModel.apiRestPasswordPlain
1624- if (! username.isNullOrEmpty() && ! password.isNullOrEmpty()) {
1625- siteSqlUtils.updateApplicationPasswordCredentialsForWPAPISite(
1626- siteModel.url, username, password
1627- )
1628- }
1629- if (payload.apiRootUrl.isNotEmpty()) {
1630- siteSqlUtils.updateWpApiRestUrlForWPAPISite(siteModel.url, payload.apiRootUrl)
1631- }
1622+ persistAppPasswordColumns(
1623+ siteModel,
1624+ { username, password ->
1625+ siteSqlUtils.updateApplicationPasswordCredentialsForWPAPISite(
1626+ siteModel.url, username, password
1627+ )
1628+ },
1629+ { siteSqlUtils.updateWpApiRestUrlForWPAPISite(siteModel.url, it) }
1630+ )
16321631 }
16331632 result
16341633 } catch (e: Exception ) {
@@ -1694,6 +1693,32 @@ open class SiteStore @Inject constructor(
16941693 OnSiteChanged (siteSqlUtils.updateXmlRpcUrl(localId, xmlRpcUrl))
16951694 }
16961695
1696+ /* *
1697+ * Persists the application-password columns that the generic full-row write excludes — the encrypted
1698+ * credentials and, when present, [SiteModel.wpApiRestUrl] — via the supplied targeted writers, returning the
1699+ * number of rows written. The three post-write handlers (UPDATE_APPLICATION_PASSWORD, the WPAPI app-password
1700+ * fetch, and app-password XML-RPC login in createOrUpdateSites) differ only in targeting the row by local id
1701+ * or by URL, so each passes its matching writer pair.
1702+ *
1703+ * wpApiRestUrl is written only alongside credentials. A credential-less /me/sites model can be a WP.com simple
1704+ * site whose getWpApiRestUrl() synthesizes a public-api proxy URL; gating on credentials keeps that synthetic
1705+ * value out of the DB (such sites never carry app-password credentials).
1706+ */
1707+ private fun persistAppPasswordColumns (
1708+ site : SiteModel ,
1709+ persistCredentials : (username: String , password: String ) -> Int ,
1710+ persistWpApiRestUrl : (wpApiRestUrl: String ) -> Int
1711+ ): Int {
1712+ val username = site.apiRestUsernamePlain
1713+ val password = site.apiRestPasswordPlain
1714+ if (username.isNullOrEmpty() || password.isNullOrEmpty()) return 0
1715+ var rowsAffected = persistCredentials(username, password)
1716+ site.wpApiRestUrl?.takeIf { it.isNotEmpty() }?.let {
1717+ rowsAffected + = persistWpApiRestUrl(it)
1718+ }
1719+ return rowsAffected
1720+ }
1721+
16971722 @Suppress(" SwallowedException" , " TooGenericExceptionCaught" )
16981723 private fun updateApplicationPassword (siteModel : SiteModel ): OnSiteChanged {
16991724 return try {
@@ -1704,16 +1729,15 @@ open class SiteStore @Inject constructor(
17041729 } else {
17051730 // Existing site: credentials + WP_API_REST_URL are excluded from the full-row write, so
17061731 // persist them on the existing row via their targeted writers (nothing else changed).
1707- val username = siteModel.apiRestUsernamePlain
1708- val password = siteModel.apiRestPasswordPlain
1709- var rowsAffected = 0
1710- if (! username.isNullOrEmpty() && ! password.isNullOrEmpty()) {
1711- rowsAffected = siteSqlUtils.updateApplicationPasswordCredentials(siteFromDB.id, username, password)
1712- }
1713- siteModel.wpApiRestUrl?.takeIf { it.isNotEmpty() }?.let {
1714- rowsAffected = siteSqlUtils.updateWpApiRestUrl(siteFromDB.id, it)
1715- }
1716- OnSiteChanged (rowsAffected)
1732+ OnSiteChanged (
1733+ persistAppPasswordColumns(
1734+ siteModel,
1735+ { username, password ->
1736+ siteSqlUtils.updateApplicationPasswordCredentials(siteFromDB.id, username, password)
1737+ },
1738+ { siteSqlUtils.updateWpApiRestUrl(siteFromDB.id, it) }
1739+ )
1740+ )
17171741 }
17181742 } catch (e: DuplicateSiteException ) {
17191743 OnSiteChanged (SiteError (DUPLICATE_SITE ))
@@ -1738,8 +1762,9 @@ open class SiteStore @Inject constructor(
17381762 } else {
17391763 // Credentials + WP_API_REST_URL are excluded from the full-row write, so clear them on the
17401764 // existing row via their targeted writers now that the application password is gone.
1741- siteSqlUtils.clearApplicationPasswordCredentials(siteFromDB.id)
1742- OnSiteChanged (siteSqlUtils.clearWpApiRestUrl(siteFromDB.id))
1765+ val rowsAffected = siteSqlUtils.clearApplicationPasswordCredentials(siteFromDB.id) +
1766+ siteSqlUtils.clearWpApiRestUrl(siteFromDB.id)
1767+ OnSiteChanged (rowsAffected)
17431768 }
17441769 } catch (e: DuplicateSiteException ) {
17451770 OnSiteChanged (SiteError (DUPLICATE_SITE ))
@@ -1805,12 +1830,14 @@ open class SiteStore @Inject constructor(
18051830 // Credentials + wpApiRestUrl are excluded from the full-row write, so when this site carries
18061831 // app-password creds (XML-RPC app-password login), persist them on the row that was just
18071832 // written (localId) via the targeted writers.
1808- val apUsername = site.apiRestUsernamePlain
1809- val apPassword = site.apiRestPasswordPlain
1810- if (localId != 0 && ! apUsername.isNullOrEmpty() && ! apPassword.isNullOrEmpty()) {
1811- siteSqlUtils.updateApplicationPasswordCredentials(localId, apUsername, apPassword)
1812- site.wpApiRestUrl?.takeIf { it.isNotEmpty() }
1813- ?.let { siteSqlUtils.updateWpApiRestUrl(localId, it) }
1833+ if (localId != 0 ) {
1834+ persistAppPasswordColumns(
1835+ site,
1836+ { username, password ->
1837+ siteSqlUtils.updateApplicationPasswordCredentials(localId, username, password)
1838+ },
1839+ { siteSqlUtils.updateWpApiRestUrl(localId, it) }
1840+ )
18141841 }
18151842 } catch (caughtException: DuplicateSiteException ) {
18161843 duplicateSiteFound = true
@@ -2475,7 +2502,7 @@ open class SiteStore @Inject constructor(
24752502 val siteFromDB = getSiteByLocalId(siteModel.id)
24762503 ? : return OnSiteChanged (SiteError (SiteErrorType .INVALID_SITE ))
24772504 // Credentials are excluded from the full-row write, so clear them on the existing row via the
2478- // targeted writer. wpApiRestUrl is intentionally preserved here (see KDoc) — rotation reuses it.
2505+ // targeted writer. wpApiRestUrl is intentionally preserved here — credential rotation reuses it.
24792506 OnSiteChanged (siteSqlUtils.clearApplicationPasswordCredentials(siteFromDB.id))
24802507 } catch (e: DuplicateSiteException ) {
24812508 OnSiteChanged (SiteError (DUPLICATE_SITE ))
0 commit comments