From 176da2bbe53e540239c9781776968d8ce59127ed Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 12 Sep 2021 13:50:16 -0400
Subject: [PATCH 1/5] Add frontend ui for aliases and migration

Ref: migrate-ui
---
 .../tabs/security_tab/security_tab.js         | 51 +++++++++-
 .../tabs/security_tab/security_tab.vue        | 96 +++++++++++++++++++
 src/services/api/api.service.js               | 49 ++++++++++
 3 files changed, 195 insertions(+), 1 deletion(-)

diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
index 65d20fc0..3f326f74 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.js
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -15,11 +15,20 @@ const SecurityTab = {
       deleteAccountError: false,
       changePasswordInputs: [ '', '', '' ],
       changedPassword: false,
-      changePasswordError: false
+      changePasswordError: false,
+      moveAccountTarget: '',
+      moveAccountPassword: '',
+      movedAccount: false,
+      moveAccountError: false,
+      aliases: [],
+      addAliasTarget: '',
+      addedAlias: false,
+      addAliasError: false
     }
   },
   created () {
     this.$store.dispatch('fetchTokens')
+    this.fetchAliases()
   },
   components: {
     ProgressButton,
@@ -92,6 +101,46 @@ const SecurityTab = {
           }
         })
     },
+    moveAccount () {
+      const params = {
+        targetAccount: this.moveAccountTarget,
+        password: this.moveAccountPassword
+      }
+      this.$store.state.api.backendInteractor.moveAccount(params)
+        .then((res) => {
+          if (res.status === 'success') {
+            this.movedAccount = true
+            this.moveAccountError = false
+          } else {
+            this.movedAccount = false
+            this.moveAccountError = res.error
+          }
+        })
+    },
+    removeAlias (alias) {
+      this.$store.state.api.backendInteractor.deleteAlias({ alias })
+        .then(() => this.fetchAliases())
+    },
+    addAlias () {
+      this.$store.state.api.backendInteractor.addAlias({ alias: this.addAliasTarget })
+        .then((res) => {
+          this.addedAlias = true
+          this.addAliasError = false
+          this.addAliasTarget = ''
+        })
+        .catch((error) => {
+          this.addedAlias = false
+          this.addAliasError = error
+        })
+        .then(() => this.fetchAliases())
+    },
+    fetchAliases () {
+      this.$store.state.api.backendInteractor.listAliases()
+        .catch(() => {})
+        .then((res) => {
+          this.aliases = res.aliases
+        })
+    },
     logout () {
       this.$store.dispatch('logout')
       this.$router.replace('/')
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index 275d4616..c6c4fa1a 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -103,6 +103,102 @@
       </table>
     </div>
     <mfa />
+
+    <div class="setting-item">
+      <h2>{{ $t('settings.account_alias') }}</h2>
+      <table>
+        <thead>
+          <tr>
+            <th>{{ $t('settings.account_alias_table_head') }}</th>
+            <th />
+          </tr>
+        </thead>
+        <tbody>
+          <tr
+            v-for="alias in aliases"
+            :key="alias"
+          >
+            <td>{{ alias }}</td>
+            <td class="actions">
+              <button
+                class="btn button-default"
+                @click="removeAlias(alias)"
+              >
+                {{ $t('settings.remove_alias') }}
+              </button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+      <div>
+        <i18n
+          path="settings.new_alias_target"
+          tag="p"
+        >
+          <code
+            place="example"
+          >
+            foo@example.org
+          </code>
+        </i18n>
+        <input
+          v-model="addAliasTarget"
+        >
+      </div>
+      <button
+        class="btn button-default"
+        @click="addAlias"
+      >
+        {{ $t('settings.save') }}
+      </button>
+      <p v-if="addedAlias">
+        {{ $t('settings.added_alias') }}
+      </p>
+      <template v-if="addAliasError !== false">
+        <p>{{ $t('settings.add_alias_error', { error: addAliasError }) }}</p>
+      </template>
+    </div>
+
+    <div class="setting-item">
+      <h2>{{ $t('settings.move_account') }}</h2>
+      <p>{{ $t('settings.move_account_notes') }}</p>
+      <div>
+        <i18n
+          path="settings.move_account_target"
+          tag="p"
+        >
+          <code
+            place="example"
+          >
+            foo@example.org
+          </code>
+        </i18n>
+        <input
+          v-model="moveAccountTarget"
+        >
+      </div>
+      <div>
+        <p>{{ $t('settings.current_password') }}</p>
+        <input
+          v-model="moveAccountPassword"
+          type="password"
+          autocomplete="current-password"
+        >
+      </div>
+      <button
+        class="btn button-default"
+        @click="moveAccount"
+      >
+        {{ $t('settings.save') }}
+      </button>
+      <p v-if="movedAccount">
+        {{ $t('settings.moved_account') }}
+      </p>
+      <template v-if="moveAccountError !== false">
+        <p>{{ $t('settings.move_account_error', { error: moveAccountError }) }}</p>
+      </template>
+    </div>
+
     <div class="setting-item">
       <h2>{{ $t('settings.delete_account') }}</h2>
       <p v-if="!deletingAccount">
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 436b8b0a..efdd8461 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -9,6 +9,8 @@ const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
 const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
+const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
+const ALIASES_URL = '/api/pleroma/aliases'
 const TAG_USER_URL = '/api/pleroma/admin/users/tag'
 const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
 const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
@@ -782,6 +784,49 @@ const changeEmail = ({ credentials, email, password }) => {
     .then((response) => response.json())
 }
 
+const moveAccount = ({ credentials, password, targetAccount }) => {
+  const form = new FormData()
+
+  form.append('password', password)
+  form.append('target_account', targetAccount)
+
+  return fetch(MOVE_ACCOUNT_URL, {
+    body: form,
+    method: 'POST',
+    headers: authHeaders(credentials)
+  })
+    .then((response) => response.json())
+}
+
+const addAlias = ({ credentials, alias }) => {
+  return promisedRequest({
+    url: ALIASES_URL,
+    method: 'PUT',
+    credentials,
+    payload: { alias }
+  })
+}
+
+const deleteAlias = ({ credentials, alias }) => {
+  return promisedRequest({
+    url: ALIASES_URL,
+    method: 'DELETE',
+    credentials,
+    payload: { alias }
+  })
+}
+
+const listAliases = ({ credentials }) => {
+  return promisedRequest({
+    url: ALIASES_URL,
+    method: 'GET',
+    credentials,
+    params: {
+      _cacheBooster: (new Date()).getTime()
+    }
+  })
+}
+
 const changePassword = ({ credentials, password, newPassword, newPasswordConfirmation }) => {
   const form = new FormData()
 
@@ -1319,6 +1364,10 @@ const apiService = {
   importFollows,
   deleteAccount,
   changeEmail,
+  moveAccount,
+  addAlias,
+  deleteAlias,
+  listAliases,
   changePassword,
   settingsMFA,
   mfaDisableOTP,

From f661099b607220dd4d63d334eb0952e785f38183 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 12 Sep 2021 13:51:32 -0400
Subject: [PATCH 2/5] Add Engilsh translation for migration

Ref: migrate-ui
---
 src/i18n/en.json | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index f8336e5c..a54fd0da 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -339,6 +339,17 @@
     "delete_account_description": "Permanently delete your data and deactivate your account.",
     "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
     "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
+    "account_alias": "Account aliases",
+    "account_alias_table_head": "Alias",
+    "remove_alias": "Remove this alias",
+    "new_alias_target": "Add a new alias (e.g. {example})",
+    "added_alias": "Alias is added.",
+    "add_alias_error": "Error adding alias: {error}",
+    "move_account": "Move account",
+    "move_account_notes": "If you want to move the account somewhere else, you must go to your target account and add an alias pointing here.",
+    "move_account_target": "Target account (e.g. {example})",
+    "moved_account": "Account is moved.",
+    "move_account_error": "Error moving account: {error}",
     "discoverable": "Allow discovery of this account in search results and other services",
     "domain_mutes": "Domains",
     "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",

From 9ffe5c2ef53cfefa96ff958a4922ec3b43e1329d Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sun, 12 Sep 2021 13:53:54 -0400
Subject: [PATCH 3/5] Add changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7cc6994..fac68ac0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Media modal now also displays description and counter position in gallery (i.e. 1/5)
 - Ability to rearrange order of attachments when uploading
 - Enabled users to zoom and pan images in media viewer with mouse and touch
+- Added frontend ui for account migration
 
 
 ## [2.4.2] - 2022-01-09

From cd9c026042f045037ff9c4f71bda2eb5dde8f3df Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 24 Mar 2022 16:55:39 -0400
Subject: [PATCH 4/5] Log errors when listing aliases

---
 .../settings_modal/tabs/security_tab/security_tab.js |  6 +++++-
 .../tabs/security_tab/security_tab.vue               | 12 ++++++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
index 3f326f74..fc732936 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.js
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -21,6 +21,7 @@ const SecurityTab = {
       movedAccount: false,
       moveAccountError: false,
       aliases: [],
+      listAliasesError: false,
       addAliasTarget: '',
       addedAlias: false,
       addAliasError: false
@@ -136,9 +137,12 @@ const SecurityTab = {
     },
     fetchAliases () {
       this.$store.state.api.backendInteractor.listAliases()
-        .catch(() => {})
         .then((res) => {
           this.aliases = res.aliases
+          this.listAliasesError = false
+        })
+        .catch((error) => {
+          this.listAliasesError = error.error
         })
     },
     logout () {
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index c6c4fa1a..c74a0c67 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -130,6 +130,18 @@
           </tr>
         </tbody>
       </table>
+      <div
+        v-if="listAliasesError"
+        class="alert error"
+      >
+        {{ $t('settings.list_aliases_error', { error }) }}
+        <FAIcon
+          class="fa-scale-110 fa-old-padding"
+          icon="times"
+          :title="$t('settings.hide_list_aliases_error_action')"
+          @click="listAliasesError = false"
+        />
+      </div>
       <div>
         <i18n
           path="settings.new_alias_target"

From 5831534800f585731264d1c4c75dd7c6faf7757d Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 24 Mar 2022 16:58:03 -0400
Subject: [PATCH 5/5] Add English translation for list aliases error

---
 src/i18n/en.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/i18n/en.json b/src/i18n/en.json
index a54fd0da..de92a14f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -341,6 +341,8 @@
     "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
     "account_alias": "Account aliases",
     "account_alias_table_head": "Alias",
+    "list_aliases_error": "Error fetching aliases: {error}",
+    "hide_list_aliases_error_action": "Close",
     "remove_alias": "Remove this alias",
     "new_alias_target": "Add a new alias (e.g. {example})",
     "added_alias": "Alias is added.",