From 4f3a220487c3c8b3596e5a8de7b65cc7c4f0c981 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Fri, 8 Mar 2019 22:40:57 +0200
Subject: [PATCH] Since BE doesn't support fetching user by screen name over
 MastoAPI we'll gonna just fetching it over QvitterAPI real quick :DDDDDDDDD

---
 src/components/block_card/block_card.js       |  2 +-
 src/components/mute_card/mute_card.js         |  2 +-
 src/components/user_profile/user_profile.js   | 63 ++++++++-----------
 src/components/user_profile/user_profile.vue  |  4 +-
 src/modules/statuses.js                       |  1 +
 src/modules/users.js                          | 12 ++--
 src/services/api/api.service.js               | 23 +++++--
 .../backend_interactor_service.js             |  5 ++
 .../specs/components/user_profile.spec.js     |  3 +-
 test/unit/specs/modules/users.spec.js         |  6 +-
 10 files changed, 65 insertions(+), 56 deletions(-)

diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js
index 11fa27b4..c459ff1b 100644
--- a/src/components/block_card/block_card.js
+++ b/src/components/block_card/block_card.js
@@ -9,7 +9,7 @@ const BlockCard = {
   },
   computed: {
     user () {
-      return this.$store.getters.userById(this.userId)
+      return this.$store.getters.findUser(this.userId)
     },
     blocked () {
       return this.user.statusnet_blocking
diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js
index 5dd0a9e5..65c9cfb5 100644
--- a/src/components/mute_card/mute_card.js
+++ b/src/components/mute_card/mute_card.js
@@ -9,7 +9,7 @@ const MuteCard = {
   },
   computed: {
     user () {
-      return this.$store.getters.userById(this.userId)
+      return this.$store.getters.findUser(this.userId)
     },
     muted () {
       return this.user.muted
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 345e7035..4f920ae2 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list'
 const FollowerList = compose(
   withLoadMore({
     fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
-    select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []),
+    select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []),
     destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
     childPropName: 'entries',
     additionalPropNames: ['userId']
@@ -20,7 +20,7 @@ const FollowerList = compose(
 const FriendList = compose(
   withLoadMore({
     fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
-    select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []),
+    select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []),
     destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
     childPropName: 'entries',
     additionalPropNames: ['userId']
@@ -31,19 +31,22 @@ const FriendList = compose(
 const UserProfile = {
   data () {
     return {
-      error: false
+      error: false,
+      fetchedUserId: null
     }
   },
   created () {
-    this.$store.commit('clearTimeline', { timeline: 'user' })
-    this.$store.commit('clearTimeline', { timeline: 'favorites' })
-    this.$store.commit('clearTimeline', { timeline: 'media' })
-    this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy })
-    this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy })
-    this.startFetchFavorites()
     if (!this.user.id) {
-      this.$store.dispatch('fetchUser', this.fetchBy)
-        .then(() => this.$store.dispatch('fetchUserRelationship', this.fetchBy))
+      let fetchPromise
+      if (this.userId) {
+        fetchPromise = this.$store.dispatch('fetchUser', this.userId)
+      } else {
+        fetchPromise = this.$store.dispatch('fetchUserByScreenName', this.userName)
+          .then(userId => {
+            this.fetchedUserId = userId
+          })
+      }
+      fetchPromise
         .catch((reason) => {
           const errorMessage = get(reason, 'error.error')
           if (errorMessage === 'No user with such user_id') { // Known error
@@ -54,8 +57,7 @@ const UserProfile = {
             this.error = this.$t('user_profile.profile_loading_error')
           }
         })
-    } else if (typeof this.user.following === 'undefined' || this.user.following === null) {
-      this.$store.dispatch('fetchUserRelationship', this.fetchBy)
+        .then(() => this.startUp())
     }
   },
   destroyed () {
@@ -72,7 +74,7 @@ const UserProfile = {
       return this.$store.state.statuses.timelines.media
     },
     userId () {
-      return this.$route.params.id || this.user.id
+      return this.$route.params.id || this.user.id || this.fetchedUserId
     },
     userName () {
       return this.$route.params.name || this.user.screen_name
@@ -82,10 +84,8 @@ const UserProfile = {
         this.userId === this.$store.state.users.currentUser.id
     },
     userInStore () {
-      if (this.isExternal) {
-        return this.$store.getters.userById(this.userId)
-      }
-      return this.$store.getters.userByName(this.userName)
+      const routeParams = this.$route.params
+      return this.$store.getters.findUser(routeParams.name || routeParams.iid)
     },
     user () {
       if (this.timeline.statuses[0]) {
@@ -96,9 +96,6 @@ const UserProfile = {
       }
       return {}
     },
-    fetchBy () {
-      return this.isExternal ? this.userId : this.userName
-    },
     isExternal () {
       return this.$route.name === 'external-user-profile'
     },
@@ -112,13 +109,13 @@ const UserProfile = {
   methods: {
     startFetchFavorites () {
       if (this.isUs) {
-        this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy })
+        this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId })
       }
     },
     startUp () {
-      this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy })
-      this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy })
-
+      this.$store.dispatch('fetchUserRelationship', this.userId)
+      this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId })
+      this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId })
       this.startFetchFavorites()
     },
     cleanUp () {
@@ -131,19 +128,11 @@ const UserProfile = {
     }
   },
   watch: {
-    userName () {
-      if (this.isExternal) {
-        return
+    userId (newVal, oldVal) {
+      if (newVal) {
+        this.cleanUp()
+        this.startUp()
       }
-      this.cleanUp()
-      this.startUp()
-    },
-    userId () {
-      if (!this.isExternal) {
-        return
-      }
-      this.cleanUp()
-      this.startUp()
     },
     $route () {
       this.$refs.tabSwitcher.activateTab(0)()
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 7d4a8b1f..d449eb85 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -11,7 +11,7 @@
         :title="$t('user_profile.timeline_title')"
         :timeline="timeline"
         :timeline-name="'user'"
-        :user-id="fetchBy"
+        :user-id="userId"
       />
       <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
         <FriendList :userId="userId" />
@@ -25,7 +25,7 @@
         :embedded="true" :title="$t('user_card.media')"
         timeline-name="media"
         :timeline="media"
-        :user-id="fetchBy"
+        :user-id="userId"
       />
       <Timeline
         v-if="isUs"
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 2b0215f0..4ee75d48 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -135,6 +135,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
   // This makes sure that user timeline won't get data meant for other
   // user. I.e. opening different user profiles makes request which could
   // return data late after user already viewing different user profile
+  console.log('TIMEINLINE', timelineObject.userId)
   if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) {
     return
   }
diff --git a/src/modules/users.js b/src/modules/users.js
index a81ed964..4e17ebf2 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -138,12 +138,7 @@ export const mutations = {
 }
 
 export const getters = {
-  userById: state => id =>
-    state.users.find(user => user.id === id),
-  userByName: state => name =>
-    state.users.find(user => user.screen_name &&
-      (user.screen_name.toLowerCase() === name.toLowerCase())
-    )
+  findUser: state => query => state.usersObject[query]
 }
 
 export const defaultState = {
@@ -165,6 +160,11 @@ const users = {
       return store.rootState.api.backendInteractor.fetchUser({ id })
         .then((user) => store.commit('addNewUsers', [user]))
     },
+    fetchUserByScreenName (store, screenName) {
+      return store.rootState.api.backendInteractor.figureOutUserId({ screenName })
+        .then((qvitterUserData) => store.rootState.api.backendInteractor.fetchUser({ id: qvitterUserData.id }))
+        .then((user) => store.commit('addNewUsers', [user]) || user.id)
+    },
     fetchUserRelationship (store, id) {
       return store.rootState.api.backendInteractor.fetchUserRelationship({ id })
         .then((relationships) => store.commit('updateUserRelationship', relationships))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 744c2f64..5a0aa2de 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -32,6 +32,7 @@ const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json
 const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
 const BLOCKING_URL = '/api/blocks/create.json'
 const UNBLOCKING_URL = '/api/blocks/destroy.json'
+const USER_URL = '/api/users/show.json'
 const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
@@ -41,7 +42,7 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
-const MASTODON_USER_URL = '/api/v1/accounts/'
+const MASTODON_USER_URL = '/api/v1/accounts'
 const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
 const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
 
@@ -272,6 +273,22 @@ const fetchUserRelationship = ({id, credentials}) => {
     })
 }
 
+// TODO remove once MastoAPI supports screen_name in fetchUser one
+const figureOutUserId = ({screenName, credentials}) => {
+  let url = `${USER_URL}/?user_id=${screenName}`
+  return fetch(url, { headers: authHeaders(credentials) })
+    .then((response) => {
+      return new Promise((resolve, reject) => response.json()
+        .then((json) => {
+          if (!response.ok) {
+            return reject(new StatusCodeError(response.status, json, { url }, response))
+          }
+          return resolve(json)
+        }))
+    })
+    .then((data) => parseUser(data))
+}
+
 const fetchFriends = ({id, page, credentials}) => {
   let url = `${FRIENDS_URL}?user_id=${id}`
   if (page) {
@@ -382,9 +399,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
   if (until) {
     params.push(['max_id', until])
   }
-  if (userId) {
-    params.push(['user_id', userId])
-  }
   if (tag) {
     url += `/${tag}.json`
   }
@@ -608,6 +622,7 @@ const apiService = {
   unblockUser,
   fetchUser,
   fetchUserRelationship,
+  figureOutUserId,
   favorite,
   unfavorite,
   retweet,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index cbd0b733..48689167 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -26,6 +26,10 @@ const backendInteractorService = (credentials) => {
     return apiService.fetchAllFollowing({username, credentials})
   }
 
+  const figureOutUserId = ({screenName}) => {
+    return apiService.figureOutUserId({screenName, credentials})
+  }
+
   const fetchUser = ({id}) => {
     return apiService.fetchUser({id, credentials})
   }
@@ -95,6 +99,7 @@ const backendInteractorService = (credentials) => {
     unfollowUser,
     blockUser,
     unblockUser,
+    figureOutUserId,
     fetchUser,
     fetchUserRelationship,
     fetchAllFollowing,
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 41fd9cd0..1524c4eb 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -13,8 +13,7 @@ const mutations = {
 }
 
 const testGetters = {
-  userByName: state => getters.userByName(state.users),
-  userById: state => getters.userById(state.users)
+  findUser: state => getters.findUser(state.users)
 }
 
 const localUser = {
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index 4d49ee24..dae7e580 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -43,7 +43,7 @@ describe('The users module', () => {
       }
       const name = 'Guy'
       const expected = { screen_name: 'Guy', id: '1' }
-      expect(getters.userByName(state)(name)).to.eql(expected)
+      expect(getters.findUser(state)(name)).to.eql(expected)
     })
 
     it('returns user with matching screen_name with different case', () => {
@@ -54,7 +54,7 @@ describe('The users module', () => {
       }
       const name = 'Guy'
       const expected = { screen_name: 'guy', id: '1' }
-      expect(getters.userByName(state)(name)).to.eql(expected)
+      expect(getters.findUser(state)(name)).to.eql(expected)
     })
   })
 
@@ -67,7 +67,7 @@ describe('The users module', () => {
       }
       const id = '1'
       const expected = { screen_name: 'Guy', id: '1' }
-      expect(getters.userById(state)(id)).to.eql(expected)
+      expect(getters.findUser(state)(id)).to.eql(expected)
     })
   })
 })