diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 895fe910c..fe65a7387 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -19,6 +19,7 @@ import { config.autoAddCss = false import App from '../App.vue' +import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import FaviconService from '../services/favicon_service/favicon_service.js' import { applyStyleConfig } from '../services/style_setter/style_setter.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js' @@ -37,7 +38,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' +import { useOAuthStore } from 'src/stores/oauth' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' @@ -260,6 +261,16 @@ const getStickers = async ({ store }) => { } } +const getAppSecret = async ({ store }) => { + const oauth = useOAuthStore() + if (oauth.userToken) { + store.commit( + 'setBackendInteractor', + backendInteractorService(oauth.getToken), + ) + } +} + const resolveStaffAccounts = ({ store, accounts }) => { const nicknames = accounts.map((uri) => uri.split('/').pop()) useInstanceStore().set({ @@ -450,13 +461,14 @@ const setConfig = async ({ store }) => { const apiConfig = configInfos[0] const staticConfig = configInfos[1] + getAppSecret({ store }) await setSettings({ store, apiConfig, staticConfig }) } const checkOAuthToken = async ({ store }) => { const oauth = useOAuthStore() - if (oauth.userToken) { - return store.dispatch('loginUser', oauth.userToken) + if (oauth.getUserToken) { + return store.dispatch('loginUser', oauth.getUserToken) } return Promise.resolve() } diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 385a6f796..3c73198c5 100644 --- a/src/components/announcements_page/announcements_page.js +++ b/src/components/announcements_page/announcements_page.js @@ -35,7 +35,7 @@ const AnnouncementsPage = { canPostAnnouncement() { return ( this.currentUser && - this.currentUser.privileges.has( + this.currentUser.privileges.includes( 'announcements_manage_announcements', ) ) diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js new file mode 100644 index 000000000..37b3f2e5e --- /dev/null +++ b/src/components/bookmark_folder_card/bookmark_folder_card.js @@ -0,0 +1,15 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' + +library.add(faEllipsisH) + +const BookmarkFolderCard = { + props: ['folder', 'allBookmarks'], + computed: { + firstLetter() { + return this.folder ? this.folder.name[0] : null + }, + }, +} + +export default BookmarkFolderCard diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.vue b/src/components/bookmark_folder_card/bookmark_folder_card.vue new file mode 100644 index 000000000..9e8bef618 --- /dev/null +++ b/src/components/bookmark_folder_card/bookmark_folder_card.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index bc30790f6..43aa239b2 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -1,10 +1,8 @@ import EmojiPicker from 'src/components/emoji_picker/emoji_picker.vue' +import apiService from '../../services/api/api.service' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { fetchBookmarkFolders } from 'src/services/api/api.service.js' const BookmarkFolderEdit = { data() { @@ -24,10 +22,8 @@ const BookmarkFolderEdit = { }, created() { if (!this.id) return - - fetchBookmarkFolders({ - credentials: useOAuthStore().token, - }).then((folders) => { + const credentials = this.$store.state.users.currentUser.credentials + apiService.fetchBookmarkFolders({ credentials }).then((folders) => { const folder = folders.find((folder) => folder.id === this.id) if (!folder) return diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js index cd12d4c3a..9fd62dae0 100644 --- a/src/components/bookmark_folders/bookmark_folders.js +++ b/src/components/bookmark_folders/bookmark_folders.js @@ -1,4 +1,4 @@ -import FolderCard from 'src/components/folder_card/folder_card.vue' +import BookmarkFolderCard from 'src/components/bookmark_folder_card/bookmark_folder_card.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' @@ -9,7 +9,7 @@ const BookmarkFolders = { } }, components: { - FolderCard, + BookmarkFolderCard, }, computed: { bookmarkFolders() { diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue index 56c9b62ce..fdd461064 100644 --- a/src/components/bookmark_folders/bookmark_folders.vue +++ b/src/components/bookmark_folders/bookmark_folders.vue @@ -12,28 +12,14 @@
-
- - - - {{ $t('nav.all_bookmarks') }} - -
- +
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index bd71c2b5d..7428c0dc7 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -17,13 +17,6 @@ import { } from './chat_layout_utils.js' import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - chatMessages, - getOrCreateChat, - sendChatMessage, -} from 'src/services/api/api.service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' @@ -122,6 +115,7 @@ const Chat = { mobileLayout: (store) => store.layoutType === 'mobile', }), ...mapState({ + backendInteractor: (state) => state.api.backendInteractor, mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, currentUser: (state) => state.users.currentUser, }), @@ -273,46 +267,42 @@ const Chat = { const fetchOlderMessages = !!maxId const sinceId = fetchLatest && chatMessageService.maxId - return chatMessages({ - id: chatId, - maxId, - sinceId, - credentials: useOAuthStore().token, - }).then((messages) => { - // Clear the current chat in case we're recovering from a ws connection loss. - if (isFirstFetch) { - chatService.clear(chatMessageService) - } + return this.backendInteractor + .chatMessages({ id: chatId, maxId, sinceId }) + .then((messages) => { + // Clear the current chat in case we're recovering from a ws connection loss. + if (isFirstFetch) { + chatService.clear(chatMessageService) + } - const positionBeforeUpdate = getScrollPosition() - this.$store - .dispatch('addChatMessages', { chatId, messages }) - .then(() => { - this.$nextTick(() => { - if (fetchOlderMessages) { - this.handleScrollUp(positionBeforeUpdate) - } + const positionBeforeUpdate = getScrollPosition() + this.$store + .dispatch('addChatMessages', { chatId, messages }) + .then(() => { + this.$nextTick(() => { + if (fetchOlderMessages) { + this.handleScrollUp(positionBeforeUpdate) + } - // In vertical screens, the first batch of fetched messages may not always take the - // full height of the scrollable container. - // If this is the case, we want to fetch the messages until the scrollable container - // is fully populated so that the user has the ability to scroll up and load the history. - if (!isScrollable() && messages.length > 0) { - this.fetchChat({ - maxId: this.currentChatMessageService.minId, - }) - } + // In vertical screens, the first batch of fetched messages may not always take the + // full height of the scrollable container. + // If this is the case, we want to fetch the messages until the scrollable container + // is fully populated so that the user has the ability to scroll up and load the history. + if (!isScrollable() && messages.length > 0) { + this.fetchChat({ + maxId: this.currentChatMessageService.minId, + }) + } + }) }) - }) - }) + }) }, async startFetching() { let chat = this.findOpenedChatByRecipientId(this.recipientId) if (!chat) { try { - chat = await getOrCreateChat({ + chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId, - credentials: useOAuthStore().token, }) } catch (e) { console.error('Error creating or getting a chat', e) @@ -379,10 +369,8 @@ const Chat = { doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { if (retriesLeft <= 0) return - sendChatMessage({ - params, - credentials: useOAuthStore().token, - }) + this.backendInteractor + .sendChatMessage(params) .then((data) => { this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, diff --git a/src/components/chat_list_item/chat_list_item.scss b/src/components/chat_list_item/chat_list_item.scss index d570c3e1b..dcef6380e 100644 --- a/src/components/chat_list_item/chat_list_item.scss +++ b/src/components/chat_list_item/chat_list_item.scss @@ -4,7 +4,6 @@ overflow: hidden; box-sizing: border-box; cursor: pointer; - width: 100%; :focus { outline: none; diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index 7b08d3163..5ee2b610d 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -3,10 +3,6 @@ import { mapGetters, mapState } from 'vuex' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { chats } from 'src/services/api/api.service.js' - import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' @@ -26,9 +22,7 @@ const chatNew = { } }, async created() { - const { chats } = await chats({ - credentials: useOAuthStore().token, - }) + const { chats } = await this.backendInteractor.chats() chats.forEach((chat) => this.suggestions.push(chat.account)) }, computed: { @@ -44,6 +38,7 @@ const chatNew = { }, ...mapState({ currentUser: (state) => state.users.currentUser, + backendInteractor: (state) => state.api.backendInteractor, }), ...mapGetters(['findUser']), }, diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 6bc0b7ad0..da15c79d6 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -9,9 +9,6 @@ import { WSConnectionStatus } from '../../services/api/api.service.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { fetchConversation, fetchStatus } from 'src/services/api/api.service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -439,26 +436,22 @@ const conversation = { methods: { fetchConversation() { if (this.status) { - fetchConversation({ - id: this.statusId, - credentials: useOAuthStore().token, - }).then(({ ancestors, descendants }) => { - this.$store.dispatch('addNewStatuses', { statuses: ancestors }) - this.$store.dispatch('addNewStatuses', { statuses: descendants }) - this.setHighlight(this.originalStatusId) - }) + this.$store.state.api.backendInteractor + .fetchConversation({ id: this.statusId }) + .then(({ ancestors, descendants }) => { + this.$store.dispatch('addNewStatuses', { statuses: ancestors }) + this.$store.dispatch('addNewStatuses', { statuses: descendants }) + this.setHighlight(this.originalStatusId) + }) } else { this.loadStatusError = null - fetchStatus({ - id: this.statusId, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .fetchStatus({ id: this.statusId }) .then((status) => { this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.fetchConversation() }) .catch((error) => { - console.error(error) this.loadStatusError = error }) } diff --git a/src/components/folder_card/folder_card.js b/src/components/folder_card/folder_card.js deleted file mode 100644 index 7a68bc3ac..000000000 --- a/src/components/folder_card/folder_card.js +++ /dev/null @@ -1,38 +0,0 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' - -library.add(faEllipsisH) - -const FolderCard = { - props: { - name: { - type: String, - required: true, - }, - emoji: { - type: String, - required: false, - default: null, - }, - emojiUrl: { - type: String, - required: false, - default: null, - }, - link: { - type: Object, - required: true, - }, - linkEdit: { - type: Object, - required: true, - }, - }, - computed: { - firstLetter() { - return this.name[0] - }, - }, -} - -export default FolderCard diff --git a/src/components/folder_card/folder_card.vue b/src/components/folder_card/folder_card.vue deleted file mode 100644 index 043a326cc..000000000 --- a/src/components/folder_card/folder_card.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - - diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 178f36894..44658e985 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -4,9 +4,6 @@ import { notificationsFromStore } from '../../services/notification_utils/notifi import BasicUserCard from '../basic_user_card/basic_user_card.vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { approveUser, denyUser } from 'src/services/api/api.service.js' const FollowRequestCard = { props: ['user'], @@ -51,10 +48,7 @@ const FollowRequestCard = { } }, doApprove() { - approveUser({ - id: this.user.id, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) const notifId = this.findFollowRequestNotificationId() @@ -76,14 +70,12 @@ const FollowRequestCard = { }, doDeny() { const notifId = this.findFollowRequestNotificationId() - - denyUser({ - id: this.user.id, - credentials: useOAuthStore().token, - }).then(() => { - this.$store.dispatch('dismissNotificationLocal', { id: notifId }) - this.$store.dispatch('removeFollowRequest', this.user) - }) + this.$store.state.api.backendInteractor + .denyUser({ id: this.user.id }) + .then(() => { + this.$store.dispatch('dismissNotificationLocal', { id: notifId }) + this.$store.dispatch('removeFollowRequest', this.user) + }) this.hideDenyConfirmDialog() }, }, diff --git a/src/components/list/list.css b/src/components/list/list.css index 365c1ee92..c478e9bc4 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -33,10 +33,6 @@ &:not(:last-child) { border-bottom: 1px dotted var(--border); } - - &:empty { - border: none; - } } .header { diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js index 7545d9126..9dcb7636c 100644 --- a/src/components/lists/lists.js +++ b/src/components/lists/lists.js @@ -1,4 +1,4 @@ -import FolderCard from 'src/components/folder_card/folder_card.vue' +import ListsCard from 'src/components/lists_card/lists_card.vue' import { useListsStore } from 'src/stores/lists.js' @@ -9,7 +9,7 @@ const Lists = { } }, components: { - FolderCard, + ListsCard, }, computed: { lists() { diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue index f3987205e..05df5b72f 100644 --- a/src/components/lists/lists.vue +++ b/src/components/lists/lists.vue @@ -14,12 +14,10 @@
-
diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js new file mode 100644 index 000000000..81b811534 --- /dev/null +++ b/src/components/lists_card/lists_card.js @@ -0,0 +1,10 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { faEllipsisH } from '@fortawesome/free-solid-svg-icons' + +library.add(faEllipsisH) + +const ListsCard = { + props: ['list'], +} + +export default ListsCard diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue new file mode 100644 index 000000000..a5dc6371e --- /dev/null +++ b/src/components/lists_card/lists_card.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 8fc77d91d..856850920 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -15,10 +15,8 @@ import { import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { approveUser, denyUser } from 'src/services/api/api.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { library } from '@fortawesome/fontawesome-svg-core' @@ -144,10 +142,7 @@ const Notification = { } }, doApprove() { - approveUser({ - id: this.user.id, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id, @@ -168,15 +163,14 @@ const Notification = { } }, doDeny() { - denyUser({ - id: this.user.id, - credentials: useOAuthStore().token, - }).then(() => { - this.$store.dispatch('dismissNotificationLocal', { - id: this.notification.id, + this.$store.state.api.backendInteractor + .denyUser({ id: this.user.id }) + .then(() => { + this.$store.dispatch('dismissNotificationLocal', { + id: this.notification.id, + }) + this.$store.dispatch('removeFollowRequest', this.user) }) - this.$store.dispatch('removeFollowRequest', this.user) - }) this.hideDenyConfirmDialog() }, }, diff --git a/src/components/remote_user_resolver/remote_user_resolver.js b/src/components/remote_user_resolver/remote_user_resolver.js index bdef5b2a4..430f56c84 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,7 +1,3 @@ -import { useOAuthStore } from 'src/stores/oauth.js' - -import { fetchUser } from 'src/services/api/api.service.js' - const RemoteUserResolver = { data: () => ({ error: false, @@ -11,11 +7,10 @@ const RemoteUserResolver = { }, methods: { redirect() { - const id = this.$route.params.username + '@' + this.$route.params.hostname - fetchUser({ - id, - credentials: useOAuthStore().token, - }) + const acct = + this.$route.params.username + '@' + this.$route.params.hostname + this.$store.state.api.backendInteractor + .fetchUser({ id: acct }) .then((externalUser) => { if (externalUser.error) { this.error = true diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js index f64128c57..98c0cb467 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.js +++ b/src/components/settings_modal/admin_tabs/emoji_tab.js @@ -11,7 +11,6 @@ import ModifiedIndicator from '../helpers/modified_indicator.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' import StringSetting from '../helpers/string_setting.vue' -import { useAdminSettingsStore } from 'src/stores/admin_settings.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -99,10 +98,10 @@ const EmojiTab = { methods: { reloadEmoji() { - useAdminSettingsStore().reloadEmoji() + this.$store.state.api.backendInteractor.reloadEmoji() }, importFromFS() { - useAdminSettingsStore().importEmojiFromFS() + this.$store.state.api.backendInteractor.importEmojiFromFS() }, emojiAddr(name) { if (this.pack.remote !== undefined) { @@ -114,7 +113,7 @@ const EmojiTab = { }, createEmojiPack() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .createEmojiPack({ name: this.newPackName }) .then((resp) => resp.json()) .then((resp) => { @@ -131,7 +130,7 @@ const EmojiTab = { }) }, deleteEmojiPack() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .deleteEmojiPack({ name: this.packName }) .then((resp) => resp.json()) .then((resp) => { @@ -158,7 +157,7 @@ const EmojiTab = { return edited !== def }, savePackMetadata() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }) .then((resp) => resp.json()) .then((resp) => { @@ -183,7 +182,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - useAdminSettingsStore().listEmojiPacks, + this.$store.state.api.backendInteractor.listEmojiPacks, ) .then((allPacks) => { this.knownLocalPacks = allPacks @@ -196,7 +195,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - useAdminSettingsStore().listRemoteEmojiPacks, + this.$store.state.api.backendInteractor.listRemoteEmojiPacks, ) .then((allPacks) => { let inst = this.remotePackInstance @@ -227,7 +226,7 @@ const EmojiTab = { this.remotePackDownloadAs = this.pack.remote.baseName } - useAdminSettingsStore() + this.$store.state.api.backendInteractor .downloadRemoteEmojiPack({ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, @@ -248,7 +247,7 @@ const EmojiTab = { }) }, downloadRemoteURLPack() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .downloadRemoteEmojiPackZIP({ url: this.remotePackURL, packName: this.newPackName, @@ -269,7 +268,7 @@ const EmojiTab = { }) }, downloadRemoteFilePack() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .downloadRemoteEmojiPackZIP({ file: this.remotePackFile[0], packName: this.newPackName, diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js index b2e6c10d0..9bec3d763 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.js +++ b/src/components/settings_modal/admin_tabs/frontends_tab.js @@ -71,7 +71,7 @@ const FrontendsTab = { const payload = { name, ref } this.working = true - useAdminSettingsStore() + this.$store.state.api.backendInteractor .installFrontend({ payload }) .finally(() => { this.working = false diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.scss b/src/components/settings_modal/admin_tabs/frontends_tab.scss index 8b7b1f4e3..e26a1b921 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.scss +++ b/src/components/settings_modal/admin_tabs/frontends_tab.scss @@ -1,6 +1,5 @@ .FrontendsTab { padding: 0 1em; - .cards-list { padding: 0; } diff --git a/src/components/settings_modal/admin_tabs/users_tab.vue b/src/components/settings_modal/admin_tabs/users_tab.vue index a40d6f995..4f6270b8d 100644 --- a/src/components/settings_modal/admin_tabs/users_tab.vue +++ b/src/components/settings_modal/admin_tabs/users_tab.vue @@ -133,7 +133,7 @@ diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue index 6cee8cebe..7078d2488 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -153,14 +153,6 @@ import Popover from 'components/popover/popover.vue' import SelectComponent from 'components/select/select.vue' import { defineAsyncComponent } from 'vue' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - addNewEmojiFile, - deleteEmojiFile, - updateEmojiFile, -} from 'src/services/api/admin.js' - export default { components: { Popover, @@ -251,14 +243,14 @@ export default { saveEditedEmoji() { if (!this.isEdited) return - updateEmojiFile({ - packName: this.packName, - shortcode: this.shortcode, - newShortcode: this.editedShortcode, - newFilename: this.editedFile, - force: false, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .updateEmojiFile({ + packName: this.packName, + shortcode: this.shortcode, + newShortcode: this.editedShortcode, + newFilename: this.editedFile, + force: false, + }) .then((resp) => { if (resp.error !== undefined) { this.$emit('displayError', resp.error) @@ -271,18 +263,18 @@ export default { }, uploadEmoji() { let packName = this.remote === undefined ? this.packName : this.copyToPack - addNewEmojiFile({ - packName: packName, - file: - this.remote === undefined - ? this.uploadURL !== '' - ? this.uploadURL - : this.uploadFile[0] - : this.emojiAddr(this.file), - shortcode: this.editedShortcode, - filename: this.editedFile, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .addNewEmojiFile({ + packName: packName, + file: + this.remote === undefined + ? this.uploadURL !== '' + ? this.uploadURL + : this.uploadFile[0] + : this.emojiAddr(this.file), + shortcode: this.editedShortcode, + filename: this.editedFile, + }) .then((resp) => resp.json()) .then((resp) => { if (resp.error !== undefined) { @@ -305,11 +297,8 @@ export default { deleteEmoji() { this.deleteModalVisible = false - deleteEmojiFile({ - packName: this.packName, - shortcode: this.shortcode, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .deleteEmojiFile({ packName: this.packName, shortcode: this.shortcode }) .then((resp) => resp.json()) .then((resp) => { if (resp.error !== undefined) { diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 77686b736..68d2b923a 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -12,9 +12,7 @@ import Preview from './old_theme_tab/theme_preview.vue' import { useInstanceStore } from 'src/stores/instance.js' import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' -import { updateProfileImages } from 'src/services/api/api.service.js' import { newImporter } from 'src/services/export_import/export_import.js' import { adoptStyleSheets, @@ -486,10 +484,8 @@ const AppearanceTab = { } this.backgroundUploading = true - updateProfileImages({ - background, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .updateProfileImages({ background }) .then((data) => { this.$store.commit('addNewUsers', [data]) this.$store.commit('setCurrentUser', data) diff --git a/src/components/settings_modal/tabs/composing_tab.js b/src/components/settings_modal/tabs/composing_tab.js index 90d80cbf6..ab8d0101d 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -14,10 +14,8 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' import { cacheKey, clearCache, emojiCacheKey } from 'src/services/sw/sw.js' @@ -166,13 +164,12 @@ const ComposingTab = { ), } - updateProfile({ - params, - credentials: useOAuthStore().token, - }).then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + this.$store.state.api.backendInteractor + .updateProfile({ params }) + .then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) }, updateFont(key, value) { useSyncConfigStore().setSimplePrefAndSave({ diff --git a/src/components/settings_modal/tabs/data_import_export_tab.js b/src/components/settings_modal/tabs/data_import_export_tab.js index 05af7d27c..cc41302c7 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -4,20 +4,8 @@ import Checkbox from 'src/components/checkbox/checkbox.vue' import Exporter from 'src/components/exporter/exporter.vue' import Importer from 'src/components/importer/importer.vue' -import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' -import { - addBackup, - exportFriends, - fetchBlocks, - fetchMutes, - importBlocks, - importFollows, - importMutes, - listBackups, -} from 'src/services/api/api.service.js' - const DataImportExportTab = { data() { return { @@ -40,51 +28,42 @@ const DataImportExportTab = { }, computed: { ...mapState({ + backendInteractor: (state) => state.api.backendInteractor, user: (state) => state.users.currentUser, }), }, methods: { getFollowsContent() { - return exportFriends({ - id: this.user.id, - credentials: useOAuthStore().token, - }).then(this.generateExportableUsersContent) + return this.backendInteractor + .exportFriends({ id: this.user.id }) + .then(this.generateExportableUsersContent) }, getBlocksContent() { - return fetchBlocks({ - credentials: useOAuthStore().token, - }).then(this.generateExportableUsersContent) + return this.backendInteractor + .fetchBlocks() + .then(this.generateExportableUsersContent) }, getMutesContent() { - return fetchMutes({ - credentials: useOAuthStore().token, - }).then(this.generateExportableUsersContent) + return this.backendInteractor + .fetchMutes() + .then(this.generateExportableUsersContent) }, importFollows(file) { - return importFollows({ - file, - credentials: useOAuthStore().token, - }).then((status) => { + return this.backendInteractor.importFollows({ file }).then((status) => { if (!status) { throw new Error('failed') } }) }, importBlocks(file) { - return importBlocks({ - file, - credentials: useOAuthStore().token, - }).then((status) => { + return this.backendInteractor.importBlocks({ file }).then((status) => { if (!status) { throw new Error('failed') } }) }, importMutes(file) { - return importMutes({ - file, - credentials: useOAuthStore().token, - }).then((status) => { + return this.backendInteractor.importMutes({ file }).then((status) => { if (!status) { throw new Error('failed') } @@ -104,9 +83,8 @@ const DataImportExportTab = { .join('\n') }, addBackup() { - addBackup({ - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .addBackup() .then(() => { this.addedBackup = true this.addBackupError = false @@ -118,9 +96,8 @@ const DataImportExportTab = { .then(() => this.fetchBackups()) }, fetchBackups() { - listBackups({ - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .listBackups() .then((res) => { this.backups = res this.listBackupsError = false diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 893833866..db1177cb2 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -12,10 +12,8 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { updateProfile } from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' const GeneralTab = { @@ -60,13 +58,12 @@ const GeneralTab = { ), } - updateProfile({ - params, - credentials: useOAuthStore().token, - }).then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + this.$store.state.api.backendInteractor + .updateProfile({ params }) + .then((user) => { + this.$store.commit('addNewUsers', [user]) + this.$store.commit('setCurrentUser', user) + }) }, updateFont(path, value) { useLocalConfigStore().set({ path, value }) diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js index d1f5bff12..0d889ce54 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -10,11 +10,8 @@ import ProgressButton from 'src/components/progress_button/progress_button.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import { useInstanceStore } from 'src/stores/instance.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js' -import { importBlocks, importFollows } from 'src/services/api/api.service.js' - const MutesAndBlocks = { data() { return { @@ -57,24 +54,22 @@ const MutesAndBlocks = { return () => this.$store.dispatch('fetch' + group, this.userId) }, importFollows(file) { - return importFollows({ - file, - credentials: useOAuthStore().token, - }).then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return this.$store.state.api.backendInteractor + .importFollows({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) }, importBlocks(file) { - return importBlocks({ - file, - credentials: useOAuthStore().token, - }).then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return this.$store.state.api.backendInteractor + .importBlocks({ file }) + .then((status) => { + if (!status) { + throw new Error('failed') + } + }) }, generateExportableUsersContent(users) { // Get addresses diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 6ef8e0ab7..5114012a8 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,10 +1,6 @@ import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { updateNotificationSettings } from 'src/services/api/api.service.js' - const NotificationsTab = { data() { return { @@ -31,8 +27,7 @@ const NotificationsTab = { }, methods: { updateNotificationSettings() { - updateNotificationSettings({ - credentials: useOAuthStore().token, + this.$store.state.api.backendInteractor.updateNotificationSettings({ settings: this.notificationSettings, }) }, diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 586ca30a8..3e12f265f 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -3,10 +3,6 @@ import UserCard from 'src/components/user_card/user_card.vue' import BooleanSetting from '../helpers/boolean_setting.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { updateProfile } from 'src/services/api/api.service.js' - import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch, @@ -39,10 +35,9 @@ const ProfileTab = { const params = { locked: this.locked, } - updateProfile({ - params, - credentials: useOAuthStore().token, - }) + + this.$store.state.api.backendInteractor + .updateProfile({ params }) .then((user) => { this.$store.commit('addNewUsers', [user]) this.$store.commit('setCurrentUser', user) diff --git a/src/components/settings_modal/tabs/security_tab/mfa.js b/src/components/settings_modal/tabs/security_tab/mfa.js index bba2a2ff9..a998fdb4b 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -5,15 +5,6 @@ import Confirm from './confirm.vue' import RecoveryCodes from './mfa_backup_codes.vue' import TOTP from './mfa_totp.vue' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - generateMfaBackupCodes, - mfaConfirmOTP, - mfaSetupOTP, - settingsMFA, -} from 'src/services/api/api.service.js' - const Mfa = { data: () => ({ settings: { @@ -80,6 +71,9 @@ const Mfa = { confirmNewBackupCodes() { return this.backupCodes.getNewCodes }, + ...mapState({ + backendInteractor: (state) => state.api.backendInteractor, + }), }, methods: { @@ -93,9 +87,7 @@ const Mfa = { this.backupCodes.inProgress = true this.backupCodes.codes = [] - return generateMfaBackupCodes({ - credentials: useOAuthStore().token, - }).then((res) => { + return this.backendInteractor.generateMfaBackupCodes().then((res) => { this.backupCodes.codes = res.codes this.backupCodes.inProgress = false }) @@ -120,9 +112,7 @@ const Mfa = { // prepare setup OTP this.setupState.state = 'setupOTP' this.setupState.setupOTPState = 'prepare' - mfaSetupOTP({ - credentials: useOAuthStore().token, - }).then((res) => { + this.backendInteractor.mfaSetupOTP().then((res) => { this.otpSettings = res this.setupState.setupOTPState = 'confirm' }) @@ -130,17 +120,18 @@ const Mfa = { doConfirmOTP() { // handler confirm enable OTP this.error = null - mfaConfirmOTP({ - token: this.otpConfirmToken, - password: this.currentPassword, - credentials: useOAuthStore().token, - }).then((res) => { - if (res.error) { - this.error = res.error - return - } - this.completeSetup() - }) + this.backendInteractor + .mfaConfirmOTP({ + token: this.otpConfirmToken, + password: this.currentPassword, + }) + .then((res) => { + if (res.error) { + this.error = res.error + return + } + this.completeSetup() + }) }, completeSetup() { @@ -161,9 +152,7 @@ const Mfa = { // fetch settings from server async fetchSettings() { - const result = await settingsMFA({ - credentials: useOAuthStore().token, - }) + const result = await this.backendInteractor.settingsMFA() if (result.error) return this.settings = result.settings this.settings.available = true diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.js b/src/components/settings_modal/tabs/security_tab/mfa_totp.js index b7a1426ab..e011fd638 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -2,10 +2,6 @@ import { mapState } from 'vuex' import Confirm from './confirm.vue' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { mfaDisableOTP } from 'src/services/api/api.service.js' - export default { props: ['settings'], data: () => ({ @@ -21,6 +17,9 @@ export default { isActivated() { return this.settings.totp }, + ...mapState({ + backendInteractor: (state) => state.api.backendInteractor, + }), }, methods: { doActivate() { @@ -37,18 +36,19 @@ export default { // confirm deactivate TOTP method this.error = null this.inProgress = true - mfaDisableOTP({ - password: this.currentPassword, - credentials: useOAuthStore().token, - }).then((res) => { - this.inProgress = false - if (res.error) { - this.error = res.error - return - } - this.deactivate = false - this.$emit('deactivate') - }) + this.backendInteractor + .mfaDisableOTP({ + password: this.currentPassword, + }) + .then((res) => { + this.inProgress = false + if (res.error) { + this.error = res.error + return + } + this.deactivate = false + this.$emit('deactivate') + }) }, }, } 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 ca959ded5..96510edcf 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -4,18 +4,8 @@ import Mfa from './mfa.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthTokensStore } from 'src/stores/oauth_tokens' -import { - addAlias, - changeEmail, - changePassword, - deleteAccount, - deleteAlias, - listAliases, - moveAccount, -} from 'src/services/api/api.service.js' import localeService from 'src/services/locale/locale.service.js' const SecurityTab = { @@ -75,79 +65,78 @@ const SecurityTab = { this.deletingAccount = true }, deleteAccount() { - deleteAccount({ - credentials: useOAuthStore().token, - password: this.deleteAccountConfirmPasswordInput, - }).then((res) => { - if (res.status === 'success') { - this.$store.dispatch('logout') - this.$router.push({ name: 'root' }) - } else { - this.deleteAccountError = res.error - } - }) + this.$store.state.api.backendInteractor + .deleteAccount({ password: this.deleteAccountConfirmPasswordInput }) + .then((res) => { + if (res.status === 'success') { + this.$store.dispatch('logout') + this.$router.push({ name: 'root' }) + } else { + this.deleteAccountError = res.error + } + }) }, changePassword() { const params = { password: this.changePasswordInputs[0], newPassword: this.changePasswordInputs[1], newPasswordConfirmation: this.changePasswordInputs[2], - credentials: useOAuthStore().token, } - changePassword(params).then((res) => { - if (res.status === 'success') { - this.changedPassword = true - this.changePasswordError = false - this.logout() - } else { - this.changedPassword = false - this.changePasswordError = res.error - } - }) + this.$store.state.api.backendInteractor + .changePassword(params) + .then((res) => { + if (res.status === 'success') { + this.changedPassword = true + this.changePasswordError = false + this.logout() + } else { + this.changedPassword = false + this.changePasswordError = res.error + } + }) }, changeEmail() { const params = { email: this.newEmail, password: this.changeEmailPassword, - credentials: useOAuthStore().token, } - changeEmail(params).then((res) => { - if (res.status === 'success') { - this.changedEmail = true - this.changeEmailError = false - } else { - this.changedEmail = false - this.changeEmailError = res.error - } - }) + this.$store.state.api.backendInteractor + .changeEmail(params) + .then((res) => { + if (res.status === 'success') { + this.changedEmail = true + this.changeEmailError = false + } else { + this.changedEmail = false + this.changeEmailError = res.error + } + }) }, moveAccount() { const params = { targetAccount: this.moveAccountTarget, password: this.moveAccountPassword, - credentials: useOAuthStore().token, } - moveAccount(params).then((res) => { - if (res.status === 'success') { - this.movedAccount = true - this.moveAccountError = false - } else { - this.movedAccount = false - this.moveAccountError = res.error - } - }) + 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) { - deleteAlias({ - alias, - credentials: useOAuthStore().token, - }).then(() => this.fetchAliases()) + this.$store.state.api.backendInteractor + .deleteAlias({ alias }) + .then(() => this.fetchAliases()) }, addAlias() { - addAlias({ - alias: this.addAliasTarget, - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .addAlias({ alias: this.addAliasTarget }) .then(() => { this.addedAlias = true this.addAliasError = false @@ -160,9 +149,8 @@ const SecurityTab = { .then(() => this.fetchAliases()) }, fetchAliases() { - listAliases({ - credentials: useOAuthStore().token, - }) + this.$store.state.api.backendInteractor + .listAliases() .then((res) => { this.aliases = res.aliases this.listAliasesError = false diff --git a/src/components/status/status.js b/src/components/status/status.js index 01d7facfc..0107bde3c 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -131,41 +131,40 @@ const Status = { Quote: defineAsyncComponent(() => import('src/components/quote/quote.vue')), StatusActionButtons, }, - props: { - statusoid: Object, - replies: Array, + props: [ + 'statusoid', + 'replies', - expandable: Boolean, - focused: Boolean, - highlight: Boolean, - compact: Boolean, - isPreview: Boolean, - noHeading: Boolean, - inlineExpanded: Boolean, - showPinned: Boolean, - inProfile: Boolean, - inConversation: Boolean, - inQuote: Boolean, + 'expandable', + 'focused', + 'highlight', + 'compact', + 'isPreview', + 'noHeading', + 'inlineExpanded', + 'showPinned', + 'inProfile', + 'inConversation', + 'inQuote', + 'profileUserId', + 'simpleTree', + 'showOtherRepliesAsButton', + 'dive', + 'ignoreMute', - profileUserId: String, - simpleTree: Boolean, - showOtherRepliesAsButton: Boolean, - dive: Function, - ignoreMute: Boolean, - - controlledThreadDisplayStatus: String, - controlledToggleThreadDisplay: Function, - controlledShowingTall: Boolean, - controlledToggleShowingTall: Function, - controlledExpandingSubject: Boolean, - controlledToggleExpandingSubject: Function, - controlledShowingLongSubject: Boolean, - controlledToggleShowingLongSubject: Function, - controlledReplying: Boolean, - controlledToggleReplying: Function, - controlledMediaPlaying: Boolean, - controlledSetMediaPlaying: Function, - }, + 'controlledThreadDisplayStatus', + 'controlledToggleThreadDisplay', + 'controlledShowingTall', + 'controlledToggleShowingTall', + 'controlledExpandingSubject', + 'controlledToggleExpandingSubject', + 'controlledShowingLongSubject', + 'controlledToggleShowingLongSubject', + 'controlledReplying', + 'controlledToggleReplying', + 'controlledMediaPlaying', + 'controlledSetMediaPlaying', + ], emits: ['goto', 'toggleExpanded'], data() { return { diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js index e02b71b0c..2d0de25ef 100644 --- a/src/components/status_action_buttons/status_action_buttons.js +++ b/src/components/status_action_buttons/status_action_buttons.js @@ -107,7 +107,7 @@ const StatusActionButtons = { button .action?.(this.funcArg) .then(() => this.$emit('onSuccess')) - .catch((err) => this.$emit('onError', err)) + .catch((err) => this.$emit('onError', err.error.error)) }, onExtraClose() { this.showPin = false diff --git a/src/components/still-image/still-image-emoji-popover.js b/src/components/still-image/still-image-emoji-popover.js index 92cc20904..02f036a52 100644 --- a/src/components/still-image/still-image-emoji-popover.js +++ b/src/components/still-image/still-image-emoji-popover.js @@ -2,7 +2,6 @@ import Popover from 'components/popover/popover.vue' import SelectComponent from 'components/select/select.vue' import { mapState } from 'pinia' -import { useAdminSettingsStore } from 'src/stores/admin_settings' import { useEmojiStore } from 'src/stores/emoji' import { useInterfaceStore } from 'src/stores/interface' @@ -38,7 +37,7 @@ export default { }) }, copyToLocalPack() { - useAdminSettingsStore() + this.$store.state.api.backendInteractor .addNewEmojiFile({ packName: this.packName, file: this.$attrs.src, diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss index 755ff9a78..30e0bd903 100644 --- a/src/components/tab_switcher/tab_switcher.scss +++ b/src/components/tab_switcher/tab_switcher.scss @@ -56,7 +56,6 @@ .contents { flex: 1 0 auto; min-height: 0; - position: relative; .hidden { display: none; diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 1a35c019b..acb2c4ea1 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -22,11 +22,9 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface' import { useMediaViewerStore } from 'src/stores/media_viewer' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { usePostStatusStore } from 'src/stores/post_status' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { updateProfile } from 'src/services/api/api.service.js' import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js' import localeService from 'src/services/locale/locale.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -309,9 +307,9 @@ export default { const privileges = this.loggedIn.privileges return ( this.loggedIn.role === 'admin' || - privileges.has('users_manage_activation_state') || - privileges.has('users_delete') || - privileges.has('users_manage_tags') + privileges.includes('users_manage_activation_state') || + privileges.includes('users_delete') || + privileges.includes('users_manage_tags') ) }, hasNote() { @@ -599,7 +597,8 @@ export default { params.header = this.newBannerFile } - updateProfile({ params }) + this.$store.state.api.backendInteractor + .updateProfile({ params }) .then((user) => { this.newFields.splice(this.newFields.length) merge(this.newFields, user.fields) diff --git a/src/components/user_profile/user_profile_admin_view.vue b/src/components/user_profile/user_profile_admin_view.vue index e5118f474..7e57aae5a 100644 --- a/src/components/user_profile/user_profile_admin_view.vue +++ b/src/components/user_profile/user_profile_admin_view.vue @@ -33,7 +33,6 @@ :statusoid="item" :in-conversation="false" :focused="false" - ignore-mute /> diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 49e569f23..12d548054 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -5,11 +5,8 @@ import List from 'src/components/list/list.vue' import Modal from 'src/components/modal/modal.vue' import UserLink from 'src/components/user_link/user_link.vue' -import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' -import { reportUser } from 'src/services/api/api.service.js' - const UserReportingModal = { components: { List, @@ -31,7 +28,6 @@ const UserReportingModal = { return !!this.$store.state.users.currentUser }, isOpen() { - console.log(this.reportModal) return this.isLoggedIn && this.reportModal.activated }, userId() { @@ -74,9 +70,9 @@ const UserReportingModal = { comment: this.comment, forward: this.forward, statusIds: [...this.statusIdsToReport], - credentials: useOAuthStore().token, } - reportUser({ ...params }) + this.$store.state.api.backendInteractor + .reportUser({ ...params }) .then(() => { this.processing = false this.resetState() diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index be4644424..8ca2d8c1a 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -2,9 +2,6 @@ import FollowCard from 'src/components/follow_card/follow_card.vue' import apiService from '../../services/api/api.service.js' import { useInstanceStore } from 'src/stores/instance.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { fetchUser, suggestions } from 'src/services/api/api.service.js' const WhoToFollow = { components: { @@ -20,22 +17,21 @@ const WhoToFollow = { }, methods: { showWhoToFollow(reply) { - reply.forEach(({ id }) => { - fetchUser({ - id, - credentials: useOAuthStore().token, - }).then((externalUser) => { - if (!externalUser.error) { - this.$store.commit('addNewUsers', [externalUser]) - this.users.push(externalUser) - } - }) + reply.forEach((i) => { + this.$store.state.api.backendInteractor + .fetchUser({ id: i.acct }) + .then((externalUser) => { + if (!externalUser.error) { + this.$store.commit('addNewUsers', [externalUser]) + this.users.push(externalUser) + } + }) }) }, getWhoToFollow() { - const credentials = useOAuthStore().token + const credentials = this.$store.state.users.currentUser.credentials if (credentials) { - suggestions({ credentials }).then((reply) => { + apiService.suggestions({ credentials }).then((reply) => { this.showWhoToFollow(reply) }) } diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index fa1c12278..cd0524ecf 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -1,10 +1,10 @@ import { shuffle } from 'lodash' +import apiService from '../../services/api/api.service.js' + import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useOAuthStore } from 'src/stores/oauth.js' -import { fetchUser, suggestions } from 'src/services/api/api.service.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' function showWhoToFollow(panel, reply) { @@ -18,15 +18,14 @@ function showWhoToFollow(panel, reply) { toFollow.img = img toFollow.name = name - fetchUser({ - id: name, - credentials: useOAuthStore().token, - }).then((externalUser) => { - if (!externalUser.error) { - panel.$store.commit('addNewUsers', [externalUser]) - toFollow.id = externalUser.id - } - }) + panel.$store.state.api.backendInteractor + .fetchUser({ id: name }) + .then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + toFollow.id = externalUser.id + } + }) }) } @@ -36,7 +35,7 @@ function getWhoToFollow(panel) { panel.usersToFollow.forEach((toFollow) => { toFollow.name = 'Loading...' }) - suggestions({ credentials }).then((reply) => { + apiService.suggestions({ credentials }).then((reply) => { showWhoToFollow(panel, reply) }) } diff --git a/src/modules/api.js b/src/modules/api.js index c7de7a070..6f4b8b15f 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,28 +1,20 @@ import { Socket } from 'phoenix' import { WSConnectionStatus } from '../services/api/api.service.js' +import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useShoutStore } from 'src/stores/shout.js' -import { - fetchTimeline, - getMastodonSocketURI, - ProcessedWS, -} from 'src/services/api/api.service.js' -import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service' -import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js' -import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js' - const retryTimeout = (multiplier) => 1000 * multiplier const api = { state: { retryMultiplier: 1, + backendInteractor: backendInteractorService(), fetchers: {}, socket: null, mastoUserSocket: null, @@ -33,6 +25,9 @@ const api = { followRequestCount: (state) => state.followRequests.length, }, mutations: { + setBackendInteractor(state, backendInteractor) { + state.backendInteractor = backendInteractor + }, addFetcher(state, { fetcherName, fetcher }) { state.fetchers[fetcherName] = fetcher }, @@ -96,17 +91,9 @@ const api = { try { const { state, commit, dispatch, rootState } = store const timelineData = rootState.statuses.timelines.friends - - const serv = useInstanceStore().server.replace('http', 'ws') - const credentials = useOAuthStore().token - const url = getMastodonSocketURI({ credentials }, serv) - - state.mastoUserSocket = ProcessedWS({ - url, - id: 'Unified', - credentials, + state.mastoUserSocket = state.backendInteractor.startUserSocket({ + store, }) - state.mastoUserSocket.addEventListener( 'pleroma:authenticated', () => { @@ -258,7 +245,7 @@ const api = { return if (store.state.fetchers[timeline]) return - const fetcher = timelineFetcher.startFetching({ + const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, @@ -266,9 +253,7 @@ const api = { statusId, bookmarkFolderId, tag, - credentials: useOAuthStore().token, }) - store.commit('addFetcher', { fetcherName: timeline, fetcher }) }, stopFetchingTimeline(store, timeline) { @@ -276,22 +261,19 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: timeline, fetcher }) }, - fetchTimeline(store, { timeline, ...rest }) { - fetchTimeline({ + store.state.backendInteractor.fetchTimeline({ store, timeline, ...rest, - credentials: useOAuthStore().token, }) }, // Notifications startFetchingNotifications(store) { if (store.state.fetchers.notifications) return - const fetcher = notificationsFetcher.startFetching({ + const fetcher = store.state.backendInteractor.startFetchingNotifications({ store, - credentials: useOAuthStore().token, }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) }, @@ -300,14 +282,19 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: 'notifications', fetcher }) }, + fetchNotifications(store, { ...rest }) { + store.state.backendInteractor.fetchNotifications({ + store, + ...rest, + }) + }, // Follow requests startFetchingFollowRequests(store) { if (store.state.fetchers.followRequests) return - const fetcher = followRequestFetcher.startFetchingFollowRequests({ - store, - credentials: useOAuthStore().token, - }) + const fetcher = store.state.backendInteractor.startFetchingFollowRequests( + { store }, + ) store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) }, @@ -316,6 +303,39 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher }) }, + removeFollowRequest(store, request) { + const requests = store.state.followRequests.filter((it) => it !== request) + store.commit('setFollowRequests', requests) + }, + + // Lists + startFetchingLists(store) { + if (store.state.fetchers.lists) return + const fetcher = store.state.backendInteractor.startFetchingLists({ + store, + }) + store.commit('addFetcher', { fetcherName: 'lists', fetcher }) + }, + stopFetchingLists(store) { + const fetcher = store.state.fetchers.lists + if (!fetcher) return + store.commit('removeFetcher', { fetcherName: 'lists', fetcher }) + }, + + // Bookmark folders + startFetchingBookmarkFolders(store) { + if (store.state.fetchers.bookmarkFolders) return + if (!useInstanceCapabilitiesStore().pleromaBookmarkFoldersAvailable) + return + const fetcher = + store.state.backendInteractor.startFetchingBookmarkFolders({ store }) + store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher }) + }, + stopFetchingBookmarkFolders(store) { + const fetcher = store.state.fetchers.bookmarkFolders + if (!fetcher) return + store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher }) + }, // Pleroma websocket setWsToken(store, token) { diff --git a/src/modules/chats.js b/src/modules/chats.js index 4ad82eccd..308b2cb27 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -9,14 +9,6 @@ import { } from '../services/entity_normalizer/entity_normalizer.service.js' import { promiseInterval } from '../services/promise_interval/promise_interval.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - chats, - deleteChatMessage, - readChat, -} from 'src/services/api/api.service.js' - const emptyChatList = () => ({ data: [], idStore: {}, @@ -44,7 +36,7 @@ const unreadChatCount = (state) => { return sumBy(state.chatList.data, 'unread') } -const chatsModule = { +const chats = { state: { ...defaultState }, getters: { currentChat: (state) => state.openedChats[state.currentChatId], @@ -59,6 +51,7 @@ const chatsModule = { // Chat list startFetchingChats({ dispatch, commit }) { const fetcher = () => dispatch('fetchChats', { latest: true }) + fetcher() commit('setChatListFetcher', { fetcher: () => promiseInterval(fetcher, 5000), }) @@ -67,10 +60,8 @@ const chatsModule = { commit('setChatListFetcher', { fetcher: undefined }) }, fetchChats({ dispatch, rootState }) { - return chats({ - credentials: useOAuthStore().token, - }).then(({ chatList }) => { - dispatch('addNewChats', { chats: chatList }) + return rootState.api.backendInteractor.chats().then(({ chats }) => { + dispatch('addNewChats', { chats }) return chats }) }, @@ -122,18 +113,11 @@ const chatsModule = { commit('readChat', { id, lastReadId }) if (isNewMessage) { - readChat({ - id, - lastReadId, - credentials: useOAuthStore().token, - }) + rootState.api.backendInteractor.readChat({ id, lastReadId }) } }, deleteChatMessage({ rootState, commit }, value) { - deleteChatMessage({ - ...value, - credentials: useOAuthStore().token, - }) + rootState.api.backendInteractor.deleteChatMessage(value) commit('deleteChatMessage', { commit, ...value }) }, resetChats({ commit, dispatch }) { @@ -278,4 +262,4 @@ const chatsModule = { }, } -export default chatsModule +export default chats diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 3d5f787cf..d501b39db 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,4 @@ -import { markNotificationsAsSeen } from '../services/api/api.service.js' +import apiService from '../services/api/api.service.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -11,12 +11,9 @@ import { import { useI18nStore } from 'src/stores/i18n.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { useReportsStore } from 'src/stores/reports.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { dismissNotification } from 'src/services/api/api.service.js' - const emptyNotifications = () => ({ desktopNotificationSilence: true, maxId: 0, @@ -157,32 +154,33 @@ export const notifications = { }, markNotificationsAsSeen({ rootState, state, commit }) { commit('markNotificationsAsSeen') - markNotificationsAsSeen({ - id: state.maxId, - credentials: rootState.users.currentUser.credentials, - }).then(() => { - closeAllDesktopNotifications(rootState) - }) + apiService + .markNotificationsAsSeen({ + id: state.maxId, + credentials: rootState.users.currentUser.credentials, + }) + .then(() => { + closeAllDesktopNotifications(rootState) + }) }, markSingleNotificationAsSeen({ rootState, commit }, { id }) { commit('markSingleNotificationAsSeen', { id }) - markNotificationsAsSeen({ - single: true, - id, - credentials: rootState.users.currentUser.credentials, - }).then(() => { - closeDesktopNotification(rootState, { id }) - }) + apiService + .markNotificationsAsSeen({ + single: true, + id, + credentials: rootState.users.currentUser.credentials, + }) + .then(() => { + closeDesktopNotification(rootState, { id }) + }) }, dismissNotificationLocal({ commit }, { id }) { commit('dismissNotification', { id }) }, dismissNotification({ rootState, commit }, { id }) { commit('dismissNotification', { id }) - dismissNotification({ - id, - credentials: useOAuthStore().token, - }) + rootState.api.backendInteractor.dismissNotification({ id }) }, updateNotification({ commit }, { id, updater }) { commit('updateNotification', { id, updater }) diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 0f125e976..90571e21d 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -1,37 +1,28 @@ import { get, set } from 'lodash' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - updateNotificationSettings, - updateProfile, -} from 'src/services/api/api.service.js' - const defaultApi = ({ rootState, commit }, { path, value }) => { const params = {} set(params, path, value) - return updateProfile({ - params, - credentials: useOAuthStore().token, - }).then((result) => { - commit('addNewUsers', [result]) - commit('setCurrentUser', result) - }) + return rootState.api.backendInteractor + .updateProfile({ params }) + .then((result) => { + commit('addNewUsers', [result]) + commit('setCurrentUser', result) + }) } const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { const settings = {} set(settings, path, value) - return updateNotificationSettings({ - settings, - credentials: useOAuthStore().token, - }).then((result) => { - if (result.status === 'success') { - commit('confirmProfileOption', { name, value }) - } else { - commit('confirmProfileOption', { name, value: oldValue }) - } - }) + return rootState.api.backendInteractor + .updateNotificationSettings({ settings }) + .then((result) => { + if (result.status === 'success') { + commit('confirmProfileOption', { name, value }) + } else { + commit('confirmProfileOption', { name, value: oldValue }) + } + }) } /** diff --git a/src/modules/statuses.js b/src/modules/statuses.js index ef6aedcbc..b4f0a93bf 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,34 +13,10 @@ import { slice, } from 'lodash' -import { - bookmarkStatus, - deleteStatus, - favorite, - fetchEmojiReactions, - fetchFavoritedByUsers, - fetchPinnedStatuses, - fetchRebloggedByUsers, - fetchScrobbles, - fetchStatus, - fetchStatusHistory, - fetchStatusSource, - muteConversation, - pinOwnStatus, - reactWithEmoji, - retweet, - search2, - unbookmarkStatus, - unfavorite, - unmuteConversation, - unpinOwnStatus, - unreactWithEmoji, - unretweet, -} from '../services/api/api.service.js' +import apiService from '../services/api/api.service.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' const emptyTl = (userId = 0) => ({ statuses: [], @@ -155,7 +131,8 @@ const getLatestScrobble = (state, user) => { state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 if (!scrobblesSupport) return - fetchScrobbles({ accountId: user.id }) + apiService + .fetchScrobbles({ accountId: user.id }) .then((scrobbles) => { if (scrobbles?.error) { useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false) @@ -625,24 +602,25 @@ const statuses = { }) }, fetchStatus({ rootState, dispatch }, id) { - return fetchStatus({ id }).then((status) => - dispatch('addNewStatuses', { statuses: [status] }), - ) + return rootState.api.backendInteractor + .fetchStatus({ id }) + .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, fetchStatusSource({ rootState }, status) { - return fetchStatusSource({ + return apiService.fetchStatusSource({ id: status.id, - credentials: useOAuthStore().token, + credentials: rootState.users.currentUser.credentials, }) }, fetchStatusHistory(_, status) { - return fetchStatusHistory({ status }) + return apiService.fetchStatusHistory({ status }) }, deleteStatus({ rootState, commit }, status) { - deleteStatus({ - id: status.id, - credentials: useOAuthStore().token, - }) + apiService + .deleteStatus({ + id: status.id, + credentials: rootState.users.currentUser.credentials, + }) .then(() => { commit('setDeleted', { status }) }) @@ -665,111 +643,99 @@ const statuses = { favorite({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) - favorite({ - id: status.id, - credentials: useOAuthStore().token, - }).then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .favorite({ id: status.id }) + .then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, unfavorite({ rootState, commit }, status) { // Optimistic unfavoriting... commit('setFavorited', { status, value: false }) - unfavorite({ - id: status.id, - credentials: useOAuthStore().token, - }).then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .unfavorite({ id: status.id }) + .then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, fetchPinnedStatuses({ rootState, dispatch }, userId) { - fetchPinnedStatuses({ - id: userId, - credentials: useOAuthStore().token, - }).then((statuses) => - dispatch('addNewStatuses', { - statuses, - timeline: 'user', - userId, - showImmediately: true, - noIdUpdate: true, - }), - ) + rootState.api.backendInteractor + .fetchPinnedStatuses({ id: userId }) + .then((statuses) => + dispatch('addNewStatuses', { + statuses, + timeline: 'user', + userId, + showImmediately: true, + noIdUpdate: true, + }), + ) }, pinStatus({ rootState, dispatch }, statusId) { - return pinOwnStatus({ - id: statusId, - credentials: useOAuthStore().token, - }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return rootState.api.backendInteractor + .pinOwnStatus({ id: statusId }) + .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, unpinStatus({ rootState, dispatch }, statusId) { - return unpinOwnStatus({ - id: statusId, - credentials: useOAuthStore().token, - }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return rootState.api.backendInteractor + .unpinOwnStatus({ id: statusId }) + .then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, muteConversation({ rootState, commit }, { id: statusId }) { - return muteConversation({ - id: statusId, - credentials: useOAuthStore().token, - }).then((status) => commit('setMutedStatus', status)) + return rootState.api.backendInteractor + .muteConversation({ id: statusId }) + .then((status) => commit('setMutedStatus', status)) }, unmuteConversation({ rootState, commit }, { id: statusId }) { - return unmuteConversation({ - id: statusId, - credentials: useOAuthStore().token, - }).then((status) => commit('setMutedStatus', status)) + return rootState.api.backendInteractor + .unmuteConversation({ id: statusId }) + .then((status) => commit('setMutedStatus', status)) }, retweet({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) - retweet({ - id: status.id, - credentials: useOAuthStore().token, - }).then((status) => - commit('setRetweetedConfirm', { - status: status.retweeted_status, - user: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .retweet({ id: status.id }) + .then((status) => + commit('setRetweetedConfirm', { + status: status.retweeted_status, + user: rootState.users.currentUser, + }), + ) }, unretweet({ rootState, commit }, status) { // Optimistic unretweeting... commit('setRetweeted', { status, value: false }) - unretweet({ - id: status.id, - credentials: useOAuthStore().token, - }).then((status) => - commit('setRetweetedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .unretweet({ id: status.id }) + .then((status) => + commit('setRetweetedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, bookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: true }) - bookmarkStatus({ - id: status.id, - folder_id: status.bookmark_folder_id, - credentials: useOAuthStore().token, - }).then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + rootState.api.backendInteractor + .bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id }) + .then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, unbookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: false }) - unbookmarkStatus({ - id: status.id, - credentials: useOAuthStore().token, - }).then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + rootState.api.backendInteractor + .unbookmarkStatus({ id: status.id }) + .then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, queueFlush({ commit }, { timeline, id }) { commit('queueFlush', { timeline, id }) @@ -779,14 +745,8 @@ const statuses = { }, fetchFavsAndRepeats({ rootState, commit }, id) { Promise.all([ - fetchFavoritedByUsers({ - id, - credentials: useOAuthStore().token, - }), - fetchRebloggedByUsers({ - id, - credentials: useOAuthStore().token, - }), + rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), + rootState.api.backendInteractor.fetchRebloggedByUsers({ id }), ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, @@ -805,11 +765,7 @@ const statuses = { if (!currentUser) return commit('addOwnReaction', { id, emoji, currentUser }) - reactWithEmoji({ - id, - emoji, - credentials: useOAuthStore().token, - }).then(() => { + rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => { dispatch('fetchEmojiReactionsBy', id) }) }, @@ -818,70 +774,59 @@ const statuses = { if (!currentUser) return commit('removeOwnReaction', { id, emoji, currentUser }) - unreactWithEmoji({ - id, - emoji, - currentUser: rootState.users.currentUser, - }).then(() => { - dispatch('fetchEmojiReactionsBy', id) - }) + rootState.api.backendInteractor + .unreactWithEmoji({ id, emoji }) + .then(() => { + dispatch('fetchEmojiReactionsBy', id) + }) }, fetchEmojiReactionsBy({ rootState, commit }, id) { - return fetchEmojiReactions({ - id, - credentials: useOAuthStore().token, - }).then((emojiReactions) => { - commit('addEmojiReactionsBy', { - id, - emojiReactions, - currentUser: rootState.users.currentUser, + return rootState.api.backendInteractor + .fetchEmojiReactions({ id }) + .then((emojiReactions) => { + commit('addEmojiReactionsBy', { + id, + emojiReactions, + currentUser: rootState.users.currentUser, + }) }) - }) }, fetchFavs({ rootState, commit }, id) { - fetchFavoritedByUsers({ - id, - credentials: useOAuthStore().token, - }).then((favoritedByUsers) => - commit('addFavs', { - id, - favoritedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .fetchFavoritedByUsers({ id }) + .then((favoritedByUsers) => + commit('addFavs', { + id, + favoritedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, fetchRepeats({ rootState, commit }, id) { - fetchRebloggedByUsers({ - id, - credentials: useOAuthStore().token, - }).then((rebloggedByUsers) => - commit('addRepeats', { - id, - rebloggedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + rootState.api.backendInteractor + .fetchRebloggedByUsers({ id }) + .then((rebloggedByUsers) => + commit('addRepeats', { + id, + rebloggedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, search(store, { q, resolve, limit, offset, following, type }) { - return search2({ - q, - resolve, - limit, - offset, - following, - type, - credentials: useOAuthStore().token, - }).then((data) => { - store.commit('addNewUsers', data.accounts) - store.commit( - 'addNewUsers', - data.statuses.map((s) => s.user).filter((u) => u), - ) - data.statuses = store.commit('addNewStatuses', { - statuses: data.statuses, + return store.rootState.api.backendInteractor + .search2({ q, resolve, limit, offset, following, type }) + .then((data) => { + store.commit('addNewUsers', data.accounts) + store.commit( + 'addNewUsers', + data.statuses.map((s) => s.user).filter((u) => u), + ) + data.statuses = store.commit('addNewStatuses', { + statuses: data.statuses, + }) + return data }) - return data - }) }, setVirtualHeight({ commit }, { statusId, height }) { commit('setVirtualHeight', { statusId, height }) diff --git a/src/modules/users.js b/src/modules/users.js index 4a4614e4d..ec70f8105 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,7 +9,8 @@ import { uniq, } from 'lodash' -import { register } from '../services/api/api.service.js' +import apiService from '../services/api/api.service.js' +import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -20,34 +21,15 @@ import { windowWidth, } from '../services/window_utils/window_utils' -import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' -import { useListsStore } from 'src/stores/lists.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' -import { - fetchBlocks, - fetchDomainMutes, - fetchFollowers, - fetchFriends, - fetchMutes, - fetchUser, - fetchUserByName, - fetchUserInLists, - fetchUserRelationship, - followUser, - getCaptcha, - muteUser, - searchUsers, - verifyCredentials, -} from 'src/services/api/api.service.js' - // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { if (!item) { @@ -90,38 +72,46 @@ const blockUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addBlockId', id) - return blockUser({ id, expiresIn }).then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addBlockId', id) + return store.rootState.api.backendInteractor + .blockUser({ id, expiresIn }) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addBlockId', id) - store.commit('removeStatus', { timeline: 'friends', userId: id }) - store.commit('removeStatus', { timeline: 'public', userId: id }) - store.commit('removeStatus', { - timeline: 'publicAndExternal', - userId: id, + store.commit('removeStatus', { timeline: 'friends', userId: id }) + store.commit('removeStatus', { timeline: 'public', userId: id }) + store.commit('removeStatus', { + timeline: 'publicAndExternal', + userId: id, + }) }) - }) } const unblockUser = (store, id) => { - return unblockUser({ id }).then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return store.rootState.api.backendInteractor + .unblockUser({ id }) + .then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const removeUserFromFollowers = (store, id) => { - return removeUserFromFollowers({ id }).then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return store.rootState.api.backendInteractor + .removeUserFromFollowers({ id }) + .then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const editUserNote = (store, { id, comment }) => { - return editUserNote({ id, comment }).then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return store.rootState.api.backendInteractor + .editUserNote({ id, comment }) + .then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } -const localMuteUser = (store, args) => { +const muteUser = (store, args) => { const id = typeof args === 'object' ? args.id : args const expiresIn = typeof args === 'object' ? args.expiresIn : 0 @@ -129,14 +119,12 @@ const localMuteUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addMuteId', id) - return muteUser({ - id, - expiresIn, - credentials: useOAuthStore().token, - }).then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addMuteId', id) - }) + return store.rootState.api.backendInteractor + .muteUser({ id, expiresIn }) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addMuteId', id) + }) } const unmuteUser = (store, id) => { @@ -144,43 +132,39 @@ const unmuteUser = (store, id) => { predictedRelationship.muting = false store.commit('updateUserRelationship', [predictedRelationship]) - return unmuteUser({ id }).then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return store.rootState.api.backendInteractor + .unmuteUser({ id }) + .then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const hideReblogs = (store, userId) => { - return followUser({ - id: userId, - reblogs: false, - credentials: useOAuthStore().token, - }).then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - }) + return store.rootState.api.backendInteractor + .followUser({ id: userId, reblogs: false }) + .then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + }) } const showReblogs = (store, userId) => { - return followUser({ - id: userId, - reblogs: true, - credentials: useOAuthStore().token, - }).then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return store.rootState.api.backendInteractor + .followUser({ id: userId, reblogs: true }) + .then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const muteDomain = (store, domain) => { - return muteDomain({ - domain, - credentials: useOAuthStore().token, - }).then(() => store.commit('addDomainMute', domain)) + return store.rootState.api.backendInteractor + .muteDomain({ domain }) + .then(() => store.commit('addDomainMute', domain)) } const unmuteDomain = (store, domain) => { - return unmuteDomain({ - domain, - credentials: useOAuthStore().token, - }).then(() => store.commit('removeDomainMute', domain)) + return store.rootState.api.backendInteractor + .unmuteDomain({ domain }) + .then(() => store.commit('removeDomainMute', domain)) } export const mutations = { @@ -401,60 +385,55 @@ const users = { }) }, fetchUser(store, id) { - return fetchUser({ - id, - credentials: useOAuthStore().token, - }).then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return store.rootState.api.backendInteractor + .fetchUser({ id }) + .then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserByName(store, name) { - return fetchUserByName({ - name, - credentials: useOAuthStore().token, - }).then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return store.rootState.api.backendInteractor + .fetchUserByName({ name }) + .then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserRelationship(store, id) { if (store.state.currentUser) { - fetchUserRelationship({ - id, - credentials: useOAuthStore().token, - }).then((relationships) => - store.commit('updateUserRelationship', relationships), - ) + store.rootState.api.backendInteractor + .fetchUserRelationship({ id }) + .then((relationships) => + store.commit('updateUserRelationship', relationships), + ) } }, fetchUserInLists(store, id) { if (store.state.currentUser) { - fetchUserInLists({ - id, - credentials: useOAuthStore().token, - }).then((inLists) => store.commit('updateUserInLists', { id, inLists })) + store.rootState.api.backendInteractor + .fetchUserInLists({ id }) + .then((inLists) => store.commit('updateUserInLists', { id, inLists })) } }, fetchBlocks(store, args) { const { reset } = args || {} const maxId = store.state.currentUser.blockIdsMaxId - return fetchBlocks({ - maxId, - credentials: useOAuthStore().token, - }).then((blocks) => { - if (reset) { - store.commit('saveBlockIds', map(blocks, 'id')) - } else { - map(blocks, 'id').map((id) => store.commit('addBlockId', id)) - } - if (blocks.length) { - store.commit('setBlockIdsMaxId', last(blocks).id) - } - store.commit('addNewUsers', blocks) - return blocks - }) + return store.rootState.api.backendInteractor + .fetchBlocks({ maxId }) + .then((blocks) => { + if (reset) { + store.commit('saveBlockIds', map(blocks, 'id')) + } else { + map(blocks, 'id').map((id) => store.commit('addBlockId', id)) + } + if (blocks.length) { + store.commit('setBlockIdsMaxId', last(blocks).id) + } + store.commit('addNewUsers', blocks) + return blocks + }) }, blockUser(store, data) { return blockUser(store, data) @@ -478,24 +457,23 @@ const users = { const { reset } = args || {} const maxId = store.state.currentUser.muteIdsMaxId - return fetchMutes({ - maxId, - credentials: useOAuthStore().token, - }).then((mutes) => { - if (reset) { - store.commit('saveMuteIds', map(mutes, 'id')) - } else { - map(mutes, 'id').map((id) => store.commit('addMuteId', id)) - } - if (mutes.length) { - store.commit('setMuteIdsMaxId', last(mutes).id) - } - store.commit('addNewUsers', mutes) - return mutes - }) + return store.rootState.api.backendInteractor + .fetchMutes({ maxId }) + .then((mutes) => { + if (reset) { + store.commit('saveMuteIds', map(mutes, 'id')) + } else { + map(mutes, 'id').map((id) => store.commit('addMuteId', id)) + } + if (mutes.length) { + store.commit('setMuteIdsMaxId', last(mutes).id) + } + store.commit('addNewUsers', mutes) + return mutes + }) }, muteUser(store, data) { - return localMuteUser(store, data) + return muteUser(store, data) }, unmuteUser(store, id) { return unmuteUser(store, id) @@ -507,18 +485,18 @@ const users = { return showReblogs(store, id) }, muteUsers(store, data = []) { - return Promise.all(data.map((d) => localMuteUser(store, d))) + return Promise.all(data.map((d) => muteUser(store, d))) }, unmuteUsers(store, ids = []) { return Promise.all(ids.map((d) => unmuteUser(store, d))) }, fetchDomainMutes(store) { - return fetchDomainMutes({ - credentials: useOAuthStore().token, - }).then((domainMutes) => { - store.commit('saveDomainMutes', domainMutes) - return domainMutes - }) + return store.rootState.api.backendInteractor + .fetchDomainMutes() + .then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) }, muteDomain(store, domain) { return muteDomain(store, domain) @@ -535,28 +513,24 @@ const users = { fetchFriends({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) - return fetchFriends({ - id, - maxId, - credentials: useOAuthStore().token, - }).then((friends) => { - commit('addNewUsers', friends) - commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) - return friends - }) + return rootState.api.backendInteractor + .fetchFriends({ id, maxId }) + .then((friends) => { + commit('addNewUsers', friends) + commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) + return friends + }) }, fetchFollowers({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.followerIds) - return fetchFollowers({ - id, - maxId, - credentials: useOAuthStore().token, - }).then((followers) => { - commit('addNewUsers', followers) - commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) - return followers - }) + return rootState.api.backendInteractor + .fetchFollowers({ id, maxId }) + .then((followers) => { + commit('addNewUsers', followers) + commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) + return followers + }) }, clearFriends({ commit }, userId) { commit('clearFriends', userId) @@ -565,22 +539,18 @@ const users = { commit('clearFollowers', userId) }, subscribeUser({ rootState, commit }, id) { - return followUser({ - id, - notify: true, - credentials: useOAuthStore().token, - }).then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return rootState.api.backendInteractor + .followUser({ id, notify: true }) + .then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, unsubscribeUser({ rootState, commit }, id) { - return followUser({ - id, - notify: false, - credentials: useOAuthStore().token, - }).then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return rootState.api.backendInteractor + .followUser({ id, notify: false }) + .then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, registerPushNotifications(store) { const token = store.state.currentUser.credentials @@ -641,13 +611,12 @@ const users = { }) }, searchUsers({ rootState, commit }, { query }) { - return searchUsers({ - query, - credentials: useOAuthStore().token, - }).then((users) => { - commit('addNewUsers', users) - return users - }) + return rootState.api.backendInteractor + .searchUsers({ query }) + .then((users) => { + commit('addNewUsers', users) + return users + }) }, async signUp(store, userInfo) { const oauthStore = useOAuthStore() @@ -655,7 +624,7 @@ const users = { try { const token = await oauthStore.ensureAppToken() - const data = await register({ + const data = await apiService.register({ credentials: token, params: { ...userInfo }, }) @@ -676,10 +645,8 @@ const users = { throw e } }, - getCaptcha(store) { - return getCaptcha({ - credentials: useOAuthStore().token, - }) + async getCaptcha(store) { + return store.rootState.api.backendInteractor.getCaptcha() }, logout(store) { @@ -703,9 +670,13 @@ const users = { store.dispatch('disconnectFromSocket') oauth.clearToken() store.dispatch('stopFetchingTimeline', 'friends') + store.commit( + 'setBackendInteractor', + backendInteractorService(oauth.getToken), + ) store.dispatch('stopFetchingNotifications') - useListsStore().stopFetching() - useBookmarkFoldersStore().stopFetching() + store.dispatch('stopFetchingLists') + store.dispatch('stopFetchingBookmarkFolders') store.dispatch('stopFetchingFollowRequests') store.commit('clearNotifications') store.commit('resetStatuses') @@ -719,12 +690,9 @@ const users = { return new Promise((resolve, reject) => { const commit = store.commit const dispatch = store.dispatch - commit('beginLogin') - - verifyCredentials({ - credentials: useOAuthStore().token, - }) + store.rootState.api.backendInteractor + .verifyCredentials(accessToken) .then((data) => { if (!data.error) { const user = data @@ -753,6 +721,12 @@ const users = { useInterfaceStore().setNotificationPermission(permission), ) + // Set our new backend interactor + commit( + 'setBackendInteractor', + backendInteractorService(accessToken), + ) + // Do server-side storage migrations // Debug snippet to clean up storage and reset migrations @@ -790,8 +764,8 @@ const users = { } } - useListsStore().startFetching() - useBookmarkFoldersStore().startFetching() + dispatch('startFetchingLists') + dispatch('startFetchingBookmarkFolders') if (user.locked) { dispatch('startFetchingFollowRequests') @@ -825,9 +799,9 @@ const users = { useInterfaceStore().setLayoutHeight(windowHeight()) // Fetch our friends - fetchFriends({ id: user.id }).then((friends) => - commit('addNewUsers', friends), - ) + store.rootState.api.backendInteractor + .fetchFriends({ id: user.id }) + .then((friends) => commit('addNewUsers', friends)) } else { const response = data.error // Authentication failed diff --git a/src/services/api/admin.js b/src/services/api/admin.js deleted file mode 100644 index 975a7386e..000000000 --- a/src/services/api/admin.js +++ /dev/null @@ -1,509 +0,0 @@ -import { promisedRequest } from './helpers.js' - -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' - -const REPORTS = '/api/v1/pleroma/admin/reports' -const CONFIG_URL = '/api/v1/pleroma/admin/config' -const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions' - -const ANNOUNCEMENTS_URL = (id = '') => - `/api/v1/pleroma/admin/announcements/${id}` - -const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' -const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install' - -const USERS_URL = (nickname = '') => `/api/v1/pleroma/admin/users/${nickname}` -const USERS_URL_LIST = ({ - page, - pageSize, - filters = {}, - query = '', - name = '', - email = '', -}) => { - const { - local = false, - external = false, - active = false, - needApproval = false, - unconfirmed = false, - deactivated = false, - isAdmin = true, - isModerator = true, - } = filters - const filters_str = [ - local && 'local', - external && 'external', - active && 'active', - needApproval && 'need_approval', - unconfirmed && 'unconfirmed', - deactivated && 'deactivated', - isAdmin && 'is_admin', - isModerator && 'is_moderator', - ] - .filter((x) => x) - .join(',') - return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` -} - -const TAG_USER_URL = '/api/pleroma/admin/users/tag' - -const PERMISSION_GROUP_URL = (right) => - `/api/pleroma/admin/users/permission_group/${right}` -const ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate' -const DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate' -const SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest' -const UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest' -const APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve' -const CONFIRM_USERS_URL = '/api/v1/pleroma/admin/users/confirm_email' -const RESEND_CONFIRMATION_EMAIL_URL = - '/api/v1/pleroma/admin/users/resend_confirmation_email' -const LIST_STATUSES_URL = ({ id, page, pageSize, godmode, withReblogs }) => - `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}` -const CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}` -const REQUIRE_PASSWORD_CHANGE_URL = - '/api/v1/pleroma/admin/users/force_password_reset' - -const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' -const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' -const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' -const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}` -const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download' -const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip' -const EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) => - `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}` -const EMOJI_UPDATE_FILE_URL = (name) => - `/api/v1/pleroma/emoji/packs/files?name=${name}` - -// - -export const setUsersTags = ({ - tags, - credentials, - value, - screen_names: nicknames, -}) => - promisedRequest({ - url: TAG_USER_URL, - method: value ? 'PUT' : 'DELETE', - credentials, - payload: { - nicknames, - tags, - }, - }) - -export const setUsersRight = ({ - right, - credentials, - value, - screen_names: nicknames, -}) => - promisedRequest({ - url: PERMISSION_GROUP_URL(right), - method: value ? 'POST' : 'DELETE', - credentials, - payload: { - nicknames, - }, - }) - -export const setUsersActivationStatus = ({ - credentials, - screen_names: nicknames, - value, -}) => - promisedRequest({ - url: value ? ACTIVATE_USERS_URL : DEACTIVATE_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) - -export const setUsersApprovalStatus = ({ - credentials, - screen_names: nicknames, -}) => - promisedRequest({ - url: APPROVE_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) - -export const setUsersConfirmationStatus = ({ - credentials, - screen_names: nicknames, -}) => - promisedRequest({ - url: CONFIRM_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) - -export const setUsersSuggestionStatus = ({ - credentials, - screen_names: nicknames, - value, -}) => - promisedRequest({ - url: value ? SUGGEST_USERS_URL : UNSUGGEST_USERS_URL, - method: 'PATCH', - credentials, - payload: { - nicknames, - }, - }).then((response) => response.users) - -export const getUserData = ({ credentials, screen_name: nickname }) => - promisedRequest({ - url: USERS_URL(nickname), - method: 'GET', - credentials, - }) - -export const deleteAccounts = ({ credentials, screen_names: nicknames }) => - promisedRequest({ - url: USERS_URL(), - method: 'DELETE', - credentials, - payload: { - nicknames, - }, - }) - -export const getAnnouncements = ({ id, credentials }) => - promisedRequest({ url: ANNOUNCEMENTS_URL(id), credentials }) - -// the reported list is hardly useful because standards are for dating i guess, -// so make sure to fetchIfMissing right afterward using this call -export const listUsers = ({ opts, credentials }) => - promisedRequest({ - url: USERS_URL_LIST(opts), - credentials, - method: 'GET', - }) - -export const resendConfirmationEmail = ({ - screen_names: nicknames, - credentials, -}) => - promisedRequest({ - url: RESEND_CONFIRMATION_EMAIL_URL, - credentials, - method: 'PATCH', - payload: { - nicknames, - }, - }) - -export const requirePasswordChange = ({ - screen_names: nicknames, - credentials, -}) => - promisedRequest({ - url: REQUIRE_PASSWORD_CHANGE_URL, - credentials, - method: 'PATCH', - payload: { - nicknames, - }, - }) - -export const disableMFA = ({ screen_name: nickname, credentials }) => - promisedRequest({ - url: DISABLE_MFA_URL, - credentials, - method: 'PUT', - payload: { - nickname, - }, - }) - -export const listStatuses = ({ opts, credentials }) => - promisedRequest({ - url: LIST_STATUSES_URL(opts), - credentials, - method: 'GET', - }) - -export const changeStatusScope = ({ - opts: { id, sensitive, visibility }, - credentials, -}) => { - var payload = {} - if (typeof sensitive !== 'undefined') { - payload['sensitive'] = sensitive - } - if (typeof visibility !== 'undefined') { - payload['visibility'] = visibility - } - - return promisedRequest({ - url: CHANGE_STATUS_SCOPE_URL(id), - credentials, - method: 'PUT', - payload, - }) -} - -export const announcementToPayload = ({ - content, - startsAt, - endsAt, - allDay, -}) => { - const payload = { content } - - if (typeof startsAt !== 'undefined') { - payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null - } - - if (typeof endsAt !== 'undefined') { - payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null - } - - if (typeof allDay !== 'undefined') { - payload.all_day = allDay - } - - return payload -} - -export const postAnnouncement = ({ - credentials, - content, - startsAt, - endsAt, - allDay, -}) => - promisedRequest({ - url: ANNOUNCEMENTS_URL(), - credentials, - method: 'POST', - payload: announcementToPayload({ content, startsAt, endsAt, allDay }), - }) - -export const editAnnouncement = ({ - id, - credentials, - content, - startsAt, - endsAt, - allDay, -}) => - promisedRequest({ - url: ANNOUNCEMENTS_URL(id), - credentials, - method: 'PATCH', - payload: announcementToPayload({ content, startsAt, endsAt, allDay }), - }) - -export const deleteAnnouncement = ({ id, credentials }) => - promisedRequest({ - url: ANNOUNCEMENTS_URL(id), - credentials, - method: 'DELETE', - }) - -export const setReportState = ({ id, state, credentials }) => { - // TODO: Can't use promisedRequest because on OK this does not return json - // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 - - return promisedRequest({ - url: REPORTS, - credentials, - method: 'PATCH', - payload: { - reports: [ - { - id, - state, - }, - ], - }, - }) - .then((data) => { - if (data.status >= 500) { - throw Error(data.statusText) - } else if (data.status >= 400) { - return data.json() - } - return data - }) - .then((data) => { - if (data.errors) { - throw Error(data.errors[0].message) - } - }) -} - -export const getInstanceDBConfig = ({ credentials }) => - promisedRequest({ - url: CONFIG_URL, - credentials, - }) - -export const getInstanceConfigDescriptions = ({ credentials }) => - promisedRequest({ - url: DESCRIPTIONS_URL, - credentials, - }) - -export const getAvailableFrontends = ({ credentials }) => - promisedRequest({ - url: FRONTENDS_URL, - credentials, - }) - -export const pushInstanceDBConfig = ({ credentials, payload }) => - promisedRequest({ - url: CONFIG_URL, - method: 'POST', - credentials, - payload, - }) - -export const installFrontend = ({ credentials, payload }) => - promisedRequest({ - url: FRONTENDS_INSTALL_URL, - credentials, - method: 'POST', - payload, - }) - -// Emoji packs -export const deleteEmojiPack = ({ name }) => - promisedRequest({ - url: EMOJI_PACK_URL(name), - method: 'DELETE', - }) - -export const reloadEmoji = ({ credentials }) => - promisedRequest({ - url: EMOJI_RELOAD_URL, - method: 'POST', - credentials, - }) - -export const importEmojiFromFS = ({ credentials }) => - promisedRequest({ - url: EMOJI_IMPORT_FS_URL, - credentials, - }) - -export const createEmojiPack = ({ name, credentials }) => - promisedRequest({ - url: EMOJI_PACK_URL(name), - method: 'POST', - credentials, - }) - -export const listRemoteEmojiPacks = ({ - instance, - page, - pageSize, - credentials, -}) => { - if (!instance.startsWith('http')) { - instance = 'https://' + instance - } - - return promisedRequest({ - url: EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize), - credentials, - }) -} - -export const downloadRemoteEmojiPack = ({ - instance, - packName, - as, - credentials, -}) => - promisedRequest({ - url: EMOJI_PACKS_DL_REMOTE_URL, - credentials, - method: 'POST', - payload: { - url: instance, - name: packName, - as, - }, - }) - -export const downloadRemoteEmojiPackZIP = ({ - url, - packName, - file, - credentials, -}) => { - const data = new FormData() - if (file) data.set('file', file) - if (url) data.set('url', url) - data.set('name', packName) - - return promisedRequest({ - url: EMOJI_PACKS_DL_REMOTE_ZIP_URL, - method: 'POST', - payload: data, - }) -} - -export const saveEmojiPackMetadata = ({ name, newData, credentials }) => - promisedRequest({ - url: EMOJI_PACK_URL(name), - credentials, - method: 'PATCH', - payload: { metadata: newData }, - }) - -export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => { - const data = new FormData() - if (filename.trim() !== '') { - data.set('filename', filename) - } - if (shortcode.trim() !== '') { - data.set('shortcode', shortcode) - } - data.set('file', file) - - return promisedRequest({ - url: EMOJI_UPDATE_FILE_URL(packName), - method: 'POST', - payload: data, - }) -} - -export const updateEmojiFile = ({ - packName, - shortcode, - newShortcode, - newFilename, - credentials, - force, -}) => - promisedRequest({ - url: EMOJI_UPDATE_FILE_URL(packName), - credentials, - method: 'PATCH', - payload: { - shortcode, - new_shortcode: newShortcode, - new_filename: newFilename, - force, - }, - }) - -export const deleteEmojiFile = ({ packName, shortcode }) => - promisedRequest({ - url: `${EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, - method: 'DELETE', - }) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 1aeeaa603..5ab1fa18e 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,9 +9,7 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { promisedRequest } from './helpers.js' - -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' +import { RegistrationError, StatusCodeError } from '../errors/errors' /* eslint-env browser */ const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' @@ -118,6 +116,12 @@ const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' +const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements' +const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements' +const PLEROMA_EDIT_ANNOUNCEMENT_URL = (id) => + `/api/v1/pleroma/admin/announcements/${id}` +const PLEROMA_DELETE_ANNOUNCEMENT_URL = (id) => + `/api/v1/pleroma/admin/announcements/${id}` const PLEROMA_SCROBBLES_URL = (id) => `/api/v1/pleroma/accounts/${id}/scrobbles` const PLEROMA_STATUS_QUOTES_URL = (id) => `/api/v1/pleroma/statuses/${id}/quotes` @@ -127,25 +131,185 @@ const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` -const EMOJI_PACKS_URL = (page, pageSize) => - `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` +const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports' +const PLEROMA_ADMIN_CONFIG_URL = '/api/v1/pleroma/admin/config' +const PLEROMA_ADMIN_DESCRIPTIONS_URL = + '/api/v1/pleroma/admin/config/descriptions' +const PLEROMA_ADMIN_FRONTENDS_URL = '/api/v1/pleroma/admin/frontends' +const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = + '/api/v1/pleroma/admin/frontends/install' -export const updateNotificationSettings = ({ credentials, settings }) => { +const PLEROMA_ADMIN_USERS_URL = '/api/v1/pleroma/admin/users' +const PLEROMA_ADMIN_USERS_URL_SHOW = (nickname) => + `/api/v1/pleroma/admin/users/${nickname}` +const PLEROMA_ADMIN_USERS_URL_LIST = ({ + page, + pageSize, + filters = {}, + query = '', + name = '', + email = '', +}) => { + const { + local = false, + external = false, + active = false, + needApproval = false, + unconfirmed = false, + deactivated = false, + isAdmin = true, + isModerator = true, + } = filters + const filters_str = [ + local && 'local', + external && 'external', + active && 'active', + needApproval && 'need_approval', + unconfirmed && 'unconfirmed', + deactivated && 'deactivated', + isAdmin && 'is_admin', + isModerator && 'is_moderator', + ] + .filter((x) => x) + .join(',') + return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}` +} +const PLEROMA_ADMIN_TAG_USER_URL = '/api/pleroma/admin/users/tag' +const PLEROMA_ADMIN_PERMISSION_GROUP_URL = (right) => + `/api/pleroma/admin/users/permission_group/${right}` +const PLEROMA_ADMIN_ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate' +const PLEROMA_ADMIN_DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate' +const PLEROMA_ADMIN_SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest' +const PLEROMA_ADMIN_UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest' +const PLEROMA_ADMIN_APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve' +const PLEROMA_ADMIN_CONFIRM_USERS_URL = + '/api/v1/pleroma/admin/users/confirm_email' +const PLEROMA_ADMIN_RESEND_CONFIRMATION_EMAIL_URL = + '/api/v1/pleroma/admin/users/resend_confirmation_email' +const PLEROMA_ADMIN_LIST_STATUSES_URL = ({ + id, + page, + pageSize, + godmode, + withReblogs, +}) => + `/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}` +const PLEROMA_ADMIN_CHANGE_STATUS_SCOPE_URL = (id) => + `/api/v1/pleroma/admin/statuses/${id}` +const PLEROMA_ADMIN_REQUIRE_PASSWORD_CHANGE_URL = + '/api/v1/pleroma/admin/users/force_password_reset' +const PLEROMA_ADMIN_DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa' +const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' +const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' +const PLEROMA_EMOJI_PACKS_URL = (page, pageSize) => + `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` +const PLEROMA_EMOJI_PACK_URL = (name) => + `/api/v1/pleroma/emoji/pack?name=${name}` +const PLEROMA_EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download' +const PLEROMA_EMOJI_PACKS_DL_REMOTE_ZIP_URL = + '/api/v1/pleroma/emoji/packs/download_zip' +const PLEROMA_EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) => + `/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}` +const PLEROMA_EMOJI_UPDATE_FILE_URL = (name) => + `/api/v1/pleroma/emoji/packs/files?name=${name}` + +const oldfetch = window.fetch + +const fetch = (url, options) => { + options = options || {} + const baseUrl = '' + const fullUrl = baseUrl + url + options.credentials = 'same-origin' + return oldfetch(fullUrl, options) +} + +const promisedRequest = ({ + method, + url, + params, + payload, + credentials, + headers = {}, +}) => { + const options = { + method, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...headers, + }, + } + if (params) { + url += + '?' + + Object.entries(params) + .map( + ([key, value]) => + encodeURIComponent(key) + '=' + encodeURIComponent(value), + ) + .join('&') + } + if (payload) { + options.body = JSON.stringify(payload) + } + if (credentials) { + options.headers = { + ...options.headers, + ...authHeaders(credentials), + } + } + return fetch(url, options).then((response) => { + return new Promise((resolve, reject) => { + // 204 is "No content", which fails to parse json (as you'd might think) + if (response.ok && response.status === 204) resolve() + + return response + .json() + .then((json) => { + if (!response.ok) { + return reject( + new StatusCodeError( + response.status, + json, + { url, options }, + response, + ), + ) + } + return resolve(json) + }) + .catch((error) => { + return reject( + new StatusCodeError( + response.status, + error, + { url, options }, + response, + ), + ) + }) + }) + }) +} + +const updateNotificationSettings = ({ credentials, settings }) => { const form = new FormData() each(settings, (value, key) => { form.append(key, value) }) - return promisedRequest({ - url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, - credentials, - method: 'PUT', - formData: form, - }) + return fetch( + `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, + { + headers: authHeaders(credentials), + method: 'PUT', + body: form, + }, + ).then((data) => data.json()) } -export const updateProfileImages = ({ +const updateProfileImages = ({ credentials, avatar = null, avatarName = null, @@ -162,20 +326,21 @@ export const updateProfileImages = ({ } if (banner !== null) form.append('header', banner) if (background !== null) form.append('pleroma_background_image', background) - return promisedRequest({ - url: MASTODON_PROFILE_UPDATE_URL, - credentials, + return fetch(MASTODON_PROFILE_UPDATE_URL, { + headers: authHeaders(credentials), method: 'PATCH', - formData: form, - }).then((data) => { - if (data.error) { - throw new Error(data.error) - } - return parseUser(data) + body: form, }) + .then((data) => data.json()) + .then((data) => { + if (data.error) { + throw new Error(data.error) + } + return parseUser(data) + }) } -export const updateProfile = ({ credentials, params }) => { +const updateProfile = ({ credentials, params }) => { const formData = new FormData() for (const name in params) { @@ -195,21 +360,23 @@ export const updateProfile = ({ credentials, params }) => { } } - return promisedRequest({ - url: MASTODON_PROFILE_UPDATE_URL, - credentials, + return fetch(MASTODON_PROFILE_UPDATE_URL, { + headers: authHeaders(credentials), method: 'PATCH', - formData, - }).then((data) => parseUser(data)) + body: formData, + }) + .then((data) => data.json()) + .then((data) => parseUser(data)) } -export const updateProfileJSON = ({ credentials, params }) => - promisedRequest({ +const updateProfileJSON = ({ credentials, params }) => { + return promisedRequest({ url: MASTODON_PROFILE_UPDATE_URL, credentials, payload: params, method: 'PATCH', }).then((data) => parseUser(data)) +} // Params needed: // nickname @@ -224,87 +391,109 @@ export const updateProfileJSON = ({ credentials, params }) => // location // token // language -export const register = ({ params, credentials }) => { +const register = ({ params, credentials }) => { const { nickname, ...rest } = params - return promisedRequest({ - url: MASTODON_REGISTRATION_URL, + return fetch(MASTODON_REGISTRATION_URL, { method: 'POST', - credentials, - payload: { + headers: { + ...authHeaders(credentials), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ nickname, locale: 'en_US', agreement: true, ...rest, - }, + }), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return response.json().then((error) => { + throw new RegistrationError(error) + }) + } }) } -export const getCaptcha = () => - promisedRequest({ - url: '/api/pleroma/captcha', - }) +const getCaptcha = () => + fetch('/api/pleroma/captcha').then((resp) => resp.json()) -export const followUser = ({ id, credentials, ...options }) => { +const authHeaders = (accessToken) => { + if (accessToken) { + return { Authorization: `Bearer ${accessToken}` } + } else { + return {} + } +} + +const followUser = ({ id, credentials, ...options }) => { + const url = MASTODON_FOLLOW_URL(id) const form = {} - if (options.reblogs !== undefined) { form.reblogs = options.reblogs } - if (options.notify !== undefined) { form.notify = options.notify } - - return promisedRequest({ - url: MASTODON_FOLLOW_URL(id), - formData: form, - credentials, + return fetch(url, { + body: JSON.stringify(form), + headers: { + ...authHeaders(credentials), + 'Content-Type': 'application/json', + }, method: 'POST', - }) + }).then((data) => data.json()) } -export const unfollowUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNFOLLOW_URL(id), - credentials, +const unfollowUser = ({ id, credentials }) => { + const url = MASTODON_UNFOLLOW_URL(id) + return fetch(url, { + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) +} -export const fetchUserInLists = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_USER_IN_LISTS(id), - credentials, - }) +const fetchUserInLists = ({ id, credentials }) => { + const url = MASTODON_USER_IN_LISTS(id) + return fetch(url, { + headers: authHeaders(credentials), + }).then((data) => data.json()) +} -export const pinOwnStatus = ({ id, credentials }) => - promisedRequest({ +const pinOwnStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) +} -export const unpinOwnStatus = ({ id, credentials }) => - promisedRequest({ +const unpinOwnStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) +} -export const muteConversation = ({ id, credentials }) => - promisedRequest({ +const muteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) +} -export const unmuteConversation = ({ id, credentials }) => - promisedRequest({ +const unmuteConversation = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) +} -export const blockUser = ({ id, expiresIn, credentials }) => { +const blockUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.duration = expiresIn @@ -318,22 +507,22 @@ export const blockUser = ({ id, expiresIn, credentials }) => { }) } -export const unblockUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_UNBLOCK_USER_URL(id), - credentials, +const unblockUser = ({ id, credentials }) => { + return fetch(MASTODON_UNBLOCK_USER_URL(id), { + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) +} -export const removeUserFromFollowers = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), - credentials, +const removeUserFromFollowers = ({ id, credentials }) => { + return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), { + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) +} -export const editUserNote = ({ id, credentials, comment }) => - promisedRequest({ +const editUserNote = ({ id, credentials, comment }) => { + return promisedRequest({ url: MASTODON_USER_NOTE_URL(id), credentials, payload: { @@ -341,29 +530,31 @@ export const editUserNote = ({ id, credentials, comment }) => }, method: 'POST', }) +} -export const approveUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_APPROVE_USER_URL(id), - credentials, +const approveUser = ({ id, credentials }) => { + const url = MASTODON_APPROVE_USER_URL(id) + return fetch(url, { + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) +} -export const denyUser = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_DENY_USER_URL(id), - credentials, +const denyUser = ({ id, credentials }) => { + const url = MASTODON_DENY_USER_URL(id) + return fetch(url, { + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) +} -export const fetchUser = ({ id, credentials }) => - promisedRequest({ - url: `${MASTODON_USER_URL}/${id}`, - credentials, - }).then((data) => parseUser(data)) +const fetchUser = ({ id, credentials }) => { + const url = `${MASTODON_USER_URL}/${id}` + return promisedRequest({ url, credentials }).then((data) => parseUser(data)) +} -export const fetchUserByName = ({ name, credentials }) => - promisedRequest({ +const fetchUserByName = ({ name, credentials }) => { + return promisedRequest({ url: MASTODON_USER_LOOKUP_URL, credentials, params: { acct: name }, @@ -379,20 +570,25 @@ export const fetchUserByName = ({ name, credentials }) => } }) .then((id) => fetchUser({ id, credentials })) +} -export const fetchUserRelationship = ({ id, credentials }) => - promisedRequest({ - url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, - credentials, +const fetchUserRelationship = ({ id, credentials }) => { + const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` + 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) + }), + ) }) +} -export const fetchFriends = ({ - id, - maxId, - sinceId, - limit = 20, - credentials, -}) => { +const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { let url = MASTODON_FOLLOWING_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -404,13 +600,12 @@ export const fetchFriends = ({ .join('&') url = url + (args ? '?' + args : '') - return promisedRequest({ - url, - credentials, - }).then((data) => data.map(parseUser)) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => data.map(parseUser)) } -export const exportFriends = ({ id, credentials }) => { +const exportFriends = ({ id, credentials }) => { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this return new Promise(async (resolve, reject) => { try { @@ -431,13 +626,7 @@ export const exportFriends = ({ id, credentials }) => { }) } -export const fetchFollowers = ({ - id, - maxId, - sinceId, - limit = 20, - credentials, -}) => { +const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { let url = MASTODON_FOLLOWERS_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -449,122 +638,263 @@ export const fetchFollowers = ({ .join('&') url += args ? '?' + args : '' - return promisedRequest({ - url, - credentials, - }).then((data) => data.map(parseUser)) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => data.map(parseUser)) } -export const fetchFollowRequests = ({ credentials }) => - promisedRequest({ - url: MASTODON_FOLLOW_REQUESTS_URL, - credentials, - }).then((data) => data.map(parseUser)) +const fetchFollowRequests = ({ credentials }) => { + const url = MASTODON_FOLLOW_REQUESTS_URL + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => data.map(parseUser)) +} -export const fetchLists = ({ credentials }) => - promisedRequest({ - url: MASTODON_LISTS_URL, - credentials, - }) +const fetchLists = ({ credentials }) => { + const url = MASTODON_LISTS_URL + return fetch(url, { headers: authHeaders(credentials) }).then((data) => + data.json(), + ) +} -export const createList = ({ title, credentials }) => - promisedRequest({ - url: MASTODON_LISTS_URL, - credentials, +const createList = ({ title, credentials }) => { + const url = MASTODON_LISTS_URL + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(url, { + headers, method: 'POST', - payload: { title }, - }) + body: JSON.stringify({ title }), + }).then((data) => data.json()) +} -export const getList = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), - credentials, - }) +const getList = ({ listId, credentials }) => { + const url = MASTODON_LIST_URL(listId) + return fetch(url, { headers: authHeaders(credentials) }).then((data) => + data.json(), + ) +} -export const updateList = ({ listId, title, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), +const updateList = ({ listId, title, credentials }) => { + const url = MASTODON_LIST_URL(listId) + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' - credentials, + return fetch(url, { + headers, method: 'PUT', - payload: { title }, + body: JSON.stringify({ title }), }) +} -export const getListAccounts = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, - }).then((data) => data.map(({ id }) => id)) +const getListAccounts = ({ listId, credentials }) => { + const url = MASTODON_LIST_ACCOUNTS_URL(listId) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => data.map(({ id }) => id)) +} -export const addAccountsToList = ({ listId, accountIds, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, +const addAccountsToList = ({ listId, accountIds, credentials }) => { + const url = MASTODON_LIST_ACCOUNTS_URL(listId) + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(url, { + headers, method: 'POST', - payload: { account_ids: accountIds }, + body: JSON.stringify({ account_ids: accountIds }), }) +} -export const removeAccountsFromList = ({ listId, accountIds, credentials }) => - promisedRequest({ - url: MASTODON_LIST_ACCOUNTS_URL(listId), - credentials, +const removeAccountsFromList = ({ listId, accountIds, credentials }) => { + const url = MASTODON_LIST_ACCOUNTS_URL(listId) + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(url, { + headers, method: 'DELETE', - payload: { account_ids: accountIds }, + body: JSON.stringify({ account_ids: accountIds }), }) +} -export const deleteList = ({ listId, credentials }) => - promisedRequest({ - url: MASTODON_LIST_URL(listId), +const deleteList = ({ listId, credentials }) => { + const url = MASTODON_LIST_URL(listId) + return fetch(url, { method: 'DELETE', - credentials, + headers: authHeaders(credentials), }) +} -export const fetchConversation = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_STATUS_CONTEXT_URL(id), - credentials, - }) +const fetchConversation = ({ id, credentials }) => { + const urlContext = MASTODON_STATUS_CONTEXT_URL(id) + return fetch(urlContext, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching timeline', data) + }) + .then((data) => data.json()) .then(({ ancestors, descendants }) => ({ ancestors: ancestors.map(parseStatus), descendants: descendants.map(parseStatus), })) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) +} -export const fetchStatus = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_STATUS_URL(id), - credentials, - }) +const fetchStatus = ({ id, credentials }) => { + const url = MASTODON_STATUS_URL(id) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching timeline', { cause: data }) + }) + .then((data) => data.json()) .then((data) => parseStatus(data)) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) +} -export const fetchStatusSource = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_STATUS_SOURCE_URL(id), - credentials, - }) +const fetchStatusSource = ({ id, credentials }) => { + const url = MASTODON_STATUS_SOURCE_URL(id) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching source', { cause: data }) + }) + .then((data) => data.json()) .then((data) => parseSource(data)) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) +} -export const fetchStatusHistory = ({ status, credentials }) => - promisedRequest({ - url: MASTODON_STATUS_HISTORY_URL(status.id), - credentials, - }).then((data) => { +const fetchStatusHistory = ({ status, credentials }) => { + const url = MASTODON_STATUS_HISTORY_URL(status.id) + return promisedRequest({ url, credentials }).then((data) => { data.reverse() return data.map((item) => { item.originalStatus = status return parseStatus(item) }) }) +} -export const fetchTimeline = ({ +const adminSetUsersTags = ({ + tags, + credentials, + value, + screen_names: nicknames, +}) => { + return promisedRequest({ + url: PLEROMA_ADMIN_TAG_USER_URL, + method: value ? 'PUT' : 'DELETE', + credentials, + payload: { + nicknames, + tags, + }, + }) +} + +const adminSetUsersRight = ({ + right, + credentials, + value, + screen_names: nicknames, +}) => { + return promisedRequest({ + url: PLEROMA_ADMIN_PERMISSION_GROUP_URL(right), + method: value ? 'POST' : 'DELETE', + credentials, + payload: { + nicknames, + }, + }) +} + +const adminSetUsersActivationStatus = ({ + credentials, + screen_names: nicknames, + value, +}) => { + return promisedRequest({ + url: value + ? PLEROMA_ADMIN_ACTIVATE_USERS_URL + : PLEROMA_ADMIN_DEACTIVATE_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) +} + +const adminSetUsersApprovalStatus = ({ + credentials, + screen_names: nicknames, +}) => { + return promisedRequest({ + url: PLEROMA_ADMIN_APPROVE_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) +} + +const adminSetUsersConfirmationStatus = ({ + credentials, + screen_names: nicknames, +}) => { + return promisedRequest({ + url: PLEROMA_ADMIN_CONFIRM_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) +} + +const adminSetUsersSuggestionStatus = ({ + credentials, + screen_names: nicknames, + value, +}) => { + return promisedRequest({ + url: value + ? PLEROMA_ADMIN_SUGGEST_USERS_URL + : PLEROMA_ADMIN_UNSUGGEST_USERS_URL, + method: 'PATCH', + credentials, + payload: { + nicknames, + }, + }).then((response) => response.users) +} + +const adminGetUserData = ({ credentials, screen_name: nickname }) => { + return promisedRequest({ + url: PLEROMA_ADMIN_USERS_URL_SHOW(nickname), + method: 'GET', + credentials, + }) +} + +const adminDeleteAccounts = ({ credentials, screen_names: nicknames }) => { + return promisedRequest({ + url: PLEROMA_ADMIN_USERS_URL, + method: 'DELETE', + credentials, + payload: { + nicknames, + }, + }) +} + +const fetchTimeline = ({ timeline, credentials, since = false, @@ -659,87 +989,109 @@ export const fetchTimeline = ({ ) url += `?${queryString}` - return promisedRequest({ - url, - credentials, - }).then(async (data) => { - const pagination = parseLinkHeaderPagination( - data._response.headers.get('Link'), - { - flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', - }, - ) + return fetch(url, { headers: authHeaders(credentials) }).then( + async (response) => { + const success = response.ok - return { - data: data.map(isNotifications ? parseNotification : parseStatus), - pagination, - } - }) + const data = await response.json() + + if (success && !data.errors) { + const pagination = parseLinkHeaderPagination( + response.headers.get('Link'), + { + flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', + }, + ) + + return { + data: data.map(isNotifications ? parseNotification : parseStatus), + pagination, + } + } else { + data.errors ||= [] + data.status = response.status + data.statusText = response.statusText + return data + } + }, + ) } -export const listEmojiPacks = ({ page, pageSize, credentials }) => - promisedRequest({ - url: EMOJI_PACKS_URL(page, pageSize), +const fetchPinnedStatuses = ({ id, credentials }) => { + const url = MASTODON_USER_TIMELINE_URL(id) + '?pinned=true' + return promisedRequest({ url, credentials }).then((data) => + data.map(parseStatus), + ) +} + +const verifyCredentials = (user) => { + return fetch(MASTODON_LOGIN_URL, { + headers: authHeaders(user), }) + .then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) + .then((data) => (data.error ? data : parseUser(data))) +} -export const fetchPinnedStatuses = ({ id, credentials }) => - promisedRequest({ - url: MASTODON_USER_TIMELINE_URL(id) + '?pinned=true', - credentials, - }).then((data) => data.map(parseStatus)) - -export const verifyCredentials = ({ credentials }) => - promisedRequest({ - url: MASTODON_LOGIN_URL, - credentials, - }).then((data) => (data.error ? data : parseUser(data))) - -export const favorite = ({ id, credentials }) => - promisedRequest({ +const favorite = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) +} -export const unfavorite = ({ id, credentials }) => - promisedRequest({ +const unfavorite = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) +} -export const retweet = ({ id, credentials }) => - promisedRequest({ +const retweet = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) +} -export const unretweet = ({ id, credentials }) => - promisedRequest({ +const unretweet = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) +} -export const bookmarkStatus = ({ id, credentials, ...options }) => - promisedRequest({ +const bookmarkStatus = ({ id, credentials, ...options }) => { + return promisedRequest({ url: MASTODON_BOOKMARK_STATUS_URL(id), - credentials, + headers: authHeaders(credentials), method: 'POST', payload: { folder_id: options.folder_id, }, }) +} -export const unbookmarkStatus = ({ id, credentials }) => - promisedRequest({ +const unbookmarkStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNBOOKMARK_STATUS_URL(id), - credentials, + headers: authHeaders(credentials), method: 'POST', }) +} -export const postStatus = ({ +const postStatus = ({ credentials, status, spoilerText, @@ -788,21 +1140,23 @@ export const postStatus = ({ form.append('preview', 'true') } - const headers = {} + const postHeaders = authHeaders(credentials) if (idempotencyKey) { - headers['idempotency-key'] = idempotencyKey + postHeaders['idempotency-key'] = idempotencyKey } - return promisedRequest({ - url: MASTODON_POST_STATUS_URL, - formData: form, + return fetch(MASTODON_POST_STATUS_URL, { + body: form, method: 'POST', - credentials, - headers, - }).then((data) => (data.error ? data : parseStatus(data))) + headers: postHeaders, + }) + .then((response) => { + return response.json() + }) + .then((data) => (data.error ? data : parseStatus(data))) } -export const editStatus = ({ +const editStatus = ({ id, credentials, status, @@ -837,131 +1191,136 @@ export const editStatus = ({ }) } - return promisedRequest({ - url: MASTODON_STATUS_URL(id), - formData: form, + const putHeaders = authHeaders(credentials) + + return fetch(MASTODON_STATUS_URL(id), { + body: form, method: 'PUT', - credentials, - }).then((data) => (data.error ? data : parseStatus(data))) + headers: putHeaders, + }) + .then((response) => { + return response.json() + }) + .then((data) => (data.error ? data : parseStatus(data))) } -export const deleteStatus = ({ id, credentials }) => - promisedRequest({ +const deleteStatus = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_DELETE_URL(id), credentials, method: 'DELETE', }) +} -export const uploadMedia = ({ formData, credentials }) => - promisedRequest({ - url: MASTODON_MEDIA_UPLOAD_URL, - formData, +const uploadMedia = ({ formData, credentials }) => { + return fetch(MASTODON_MEDIA_UPLOAD_URL, { + body: formData, method: 'POST', - credentials, - }).then((data) => parseAttachment(data)) + headers: authHeaders(credentials), + }) + .then((data) => data.json()) + .then((data) => parseAttachment(data)) +} -export const setMediaDescription = ({ id, description, credentials }) => - promisedRequest({ +const setMediaDescription = ({ id, description, credentials }) => { + return promisedRequest({ url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`, method: 'PUT', - credentials, + headers: authHeaders(credentials), payload: { description, }, }).then((data) => parseAttachment(data)) +} -export const importMutes = ({ file, credentials }) => { +const importMutes = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return promisedRequest({ - url: MUTES_IMPORT_URL, - formData, + return fetch(MUTES_IMPORT_URL, { + body: formData, method: 'POST', - credentials, + headers: authHeaders(credentials), }).then((response) => response.ok) } -export const importBlocks = ({ file, credentials }) => { +const importBlocks = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return promisedRequest({ - url: BLOCKS_IMPORT_URL, - formData, + return fetch(BLOCKS_IMPORT_URL, { + body: formData, method: 'POST', - credentials, + headers: authHeaders(credentials), }).then((response) => response.ok) } -export const importFollows = ({ file, credentials }) => { +const importFollows = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return promisedRequest({ - url: FOLLOW_IMPORT_URL, - formData, + return fetch(FOLLOW_IMPORT_URL, { + body: formData, method: 'POST', - credentials, + headers: authHeaders(credentials), }).then((response) => response.ok) } -export const deleteAccount = ({ credentials, password }) => { - const formData = new FormData() +const deleteAccount = ({ credentials, password }) => { + const form = new FormData() - formData.append('password', password) + form.append('password', password) - return promisedRequest({ - url: DELETE_ACCOUNT_URL, - formData, + return fetch(DELETE_ACCOUNT_URL, { + body: form, method: 'POST', - credentials, - }) + headers: authHeaders(credentials), + }).then((response) => response.json()) } -export const changeEmail = ({ credentials, email, password }) => { +const changeEmail = ({ credentials, email, password }) => { const form = new FormData() form.append('email', email) form.append('password', password) - return promisedRequest({ - url: CHANGE_EMAIL_URL, - formData: form, + return fetch(CHANGE_EMAIL_URL, { + body: form, method: 'POST', - credentials, - }) + headers: authHeaders(credentials), + }).then((response) => response.json()) } -export const moveAccount = ({ credentials, password, targetAccount }) => { +const moveAccount = ({ credentials, password, targetAccount }) => { const form = new FormData() form.append('password', password) form.append('target_account', targetAccount) - return promisedRequest({ - url: MOVE_ACCOUNT_URL, - formData: form, + return fetch(MOVE_ACCOUNT_URL, { + body: form, method: 'POST', - credentials, - }) + headers: authHeaders(credentials), + }).then((response) => response.json()) } -export const addAlias = ({ credentials, alias }) => - promisedRequest({ +const addAlias = ({ credentials, alias }) => { + return promisedRequest({ url: ALIASES_URL, method: 'PUT', credentials, payload: { alias }, }) +} -export const deleteAlias = ({ credentials, alias }) => - promisedRequest({ +const deleteAlias = ({ credentials, alias }) => { + return promisedRequest({ url: ALIASES_URL, method: 'DELETE', credentials, payload: { alias }, }) +} -export const listAliases = ({ credentials }) => - promisedRequest({ +const listAliases = ({ credentials }) => { + return promisedRequest({ url: ALIASES_URL, method: 'GET', credentials, @@ -969,8 +1328,9 @@ export const listAliases = ({ credentials }) => _cacheBooster: new Date().getTime(), }, }) +} -export const changePassword = ({ +const changePassword = ({ credentials, password, newPassword, @@ -982,61 +1342,58 @@ export const changePassword = ({ form.append('new_password', newPassword) form.append('new_password_confirmation', newPasswordConfirmation) - return promisedRequest({ - url: CHANGE_PASSWORD_URL, - formData: form, + return fetch(CHANGE_PASSWORD_URL, { + body: form, method: 'POST', - credentials, - }) + headers: authHeaders(credentials), + }).then((response) => response.json()) } -export const settingsMFA = ({ credentials }) => - promisedRequest({ - url: MFA_SETTINGS_URL, - credentials, +const settingsMFA = ({ credentials }) => { + return fetch(MFA_SETTINGS_URL, { + headers: authHeaders(credentials), method: 'GET', - }) + }).then((data) => data.json()) +} -export const mfaDisableOTP = ({ credentials, password }) => { +const mfaDisableOTP = ({ credentials, password }) => { const form = new FormData() form.append('password', password) - return promisedRequest({ - url: MFA_DISABLE_OTP_URL, - formData: form, + return fetch(MFA_DISABLE_OTP_URL, { + body: form, method: 'DELETE', - credentials, - }) + headers: authHeaders(credentials), + }).then((response) => response.json()) } -export const mfaConfirmOTP = ({ credentials, password, token }) => { +const mfaConfirmOTP = ({ credentials, password, token }) => { const form = new FormData() form.append('password', password) form.append('code', token) - return promisedRequest({ - url: MFA_CONFIRM_OTP_URL, - formData: form, - credentials, + return fetch(MFA_CONFIRM_OTP_URL, { + body: form, + headers: authHeaders(credentials), method: 'POST', - }) + }).then((data) => data.json()) } -export const mfaSetupOTP = ({ credentials }) => - promisedRequest({ - url: MFA_SETUP_OTP_URL, - credentials, +const mfaSetupOTP = ({ credentials }) => { + return fetch(MFA_SETUP_OTP_URL, { + headers: authHeaders(credentials), method: 'GET', - }) -export const generateMfaBackupCodes = ({ credentials }) => - promisedRequest({ - url: MFA_BACKUP_CODES_URL, - credentials, + }).then((data) => data.json()) +} +const generateMfaBackupCodes = ({ credentials }) => { + return fetch(MFA_BACKUP_CODES_URL, { + headers: authHeaders(credentials), method: 'GET', - }) + }).then((data) => data.json()) +} -export const fetchMutes = ({ maxId, credentials }) => { +const fetchMutes = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1047,7 +1404,7 @@ export const fetchMutes = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -export const muteUser = ({ id, expiresIn, credentials }) => { +const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.expires_in = expiresIn @@ -1061,14 +1418,15 @@ export const muteUser = ({ id, expiresIn, credentials }) => { }) } -export const unmuteUser = ({ id, credentials }) => - promisedRequest({ +const unmuteUser = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST', }) +} -export const fetchBlocks = ({ maxId, credentials }) => { +const fetchBlocks = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1079,15 +1437,16 @@ export const fetchBlocks = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -export const addBackup = ({ credentials }) => - promisedRequest({ +const addBackup = ({ credentials }) => { + return promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'POST', credentials, }) +} -export const listBackups = ({ credentials }) => - promisedRequest({ +const listBackups = ({ credentials }) => { + return promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'GET', credentials, @@ -1095,48 +1454,53 @@ export const listBackups = ({ credentials }) => _cacheBooster: new Date().getTime(), }, }) +} -export const fetchOAuthTokens = ({ credentials }) => - promisedRequest({ - url: '/api/oauth_tokens.json', - credentials, - }) +const fetchOAuthTokens = ({ credentials }) => { + const url = '/api/oauth_tokens.json' -export const revokeOAuthToken = ({ id, credentials }) => - promisedRequest({ - url: `/api/oauth_tokens/${id}`, - credentials, - method: 'DELETE', - }) - -export const suggestions = ({ credentials }) => - promisedRequest({ - url: SUGGESTIONS_URL, - credentials, - }) - -export const markNotificationsAsSeen = ({ - id, - credentials, - single = false, -}) => { - const formData = new FormData() - - if (single) { - formData.append('id', id) - } else { - formData.append('max_id', id) - } - - return promisedRequest({ - url: NOTIFICATION_READ_URL, - formData, - credentials, - method: 'POST', + return fetch(url, { + headers: authHeaders(credentials), + }).then((data) => { + if (data.ok) { + return data.json() + } + throw new Error('Error fetching auth tokens', data) }) } -export const vote = ({ pollId, choices, credentials }) => { +const revokeOAuthToken = ({ id, credentials }) => { + const url = `/api/oauth_tokens/${id}` + + return fetch(url, { + headers: authHeaders(credentials), + method: 'DELETE', + }) +} + +const suggestions = ({ credentials }) => { + return fetch(SUGGESTIONS_URL, { + headers: authHeaders(credentials), + }).then((data) => data.json()) +} + +const markNotificationsAsSeen = ({ id, credentials, single = false }) => { + const body = new FormData() + + if (single) { + body.append('id', id) + } else { + body.append('max_id', id) + } + + return fetch(NOTIFICATION_READ_URL, { + body, + headers: authHeaders(credentials), + method: 'POST', + }).then((data) => data.json()) +} + +const vote = ({ pollId, choices, credentials }) => { const form = new FormData() form.append('choices', choices) @@ -1150,29 +1514,32 @@ export const vote = ({ pollId, choices, credentials }) => { }) } -export const fetchPoll = ({ pollId, credentials }) => - promisedRequest({ +const fetchPoll = ({ pollId, credentials }) => { + return promisedRequest({ url: MASTODON_POLL_URL(encodeURIComponent(pollId)), method: 'GET', credentials, }) +} -export const fetchFavoritedByUsers = ({ id, credentials }) => - promisedRequest({ +const fetchFavoritedByUsers = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) +} -export const fetchRebloggedByUsers = ({ id, credentials }) => - promisedRequest({ +const fetchRebloggedByUsers = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) +} -export const fetchEmojiReactions = ({ id, credentials }) => - promisedRequest({ +const fetchEmojiReactions = ({ id, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials, }).then((reactions) => @@ -1181,29 +1548,26 @@ export const fetchEmojiReactions = ({ id, credentials }) => return r }), ) +} -export const reactWithEmoji = ({ id, emoji, credentials }) => - promisedRequest({ +const reactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_REACT_URL(id, emoji), method: 'PUT', credentials, }).then(parseStatus) +} -export const unreactWithEmoji = ({ id, emoji, credentials }) => - promisedRequest({ +const unreactWithEmoji = ({ id, emoji, credentials }) => { + return promisedRequest({ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), method: 'DELETE', credentials, }).then(parseStatus) +} -export const reportUser = ({ - credentials, - userId, - statusIds, - comment, - forward, -}) => - promisedRequest({ +const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { + return promisedRequest({ url: MASTODON_REPORT_USER_URL, method: 'POST', payload: { @@ -1214,9 +1578,10 @@ export const reportUser = ({ }, credentials, }) +} -export const searchUsers = ({ credentials, query }) => - promisedRequest({ +const searchUsers = ({ credentials, query }) => { + return promisedRequest({ url: MASTODON_USER_SEARCH_URL, params: { q: query, @@ -1224,8 +1589,9 @@ export const searchUsers = ({ credentials, query }) => }, credentials, }).then((data) => data.map(parseUser)) +} -export const search2 = ({ +const search2 = ({ credentials, q, resolve, @@ -1268,59 +1634,214 @@ export const search2 = ({ ) url += `?${queryString}` - return promisedRequest({ - url, - credentials, - }) + return fetch(url, { headers: authHeaders(credentials) }) + .then((data) => { + if (data.ok) { + return data + } + throw new Error('Error fetching search result', data) + }) + .then((data) => { + return data.json() + }) .then((data) => { data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u)) data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s)) return data }) - .catch((error) => { - throw new Error('Error fetching timeline', error) - }) } -export const fetchKnownDomains = ({ credentials }) => - promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) +const fetchKnownDomains = ({ credentials }) => { + return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) +} -export const fetchDomainMutes = ({ credentials }) => - promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) +const fetchDomainMutes = ({ credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) +} -export const muteDomain = ({ domain, credentials }) => - promisedRequest({ +const muteDomain = ({ domain, credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'POST', payload: { domain }, credentials, }) +} -export const unmuteDomain = ({ domain, credentials }) => - promisedRequest({ +const unmuteDomain = ({ domain, credentials }) => { + return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'DELETE', payload: { domain }, credentials, }) +} -export const dismissNotification = ({ credentials, id }) => - promisedRequest({ +const dismissNotification = ({ credentials, id }) => { + return promisedRequest({ url: MASTODON_DISMISS_NOTIFICATION_URL(id), method: 'POST', payload: { id }, credentials, }) +} -export const getAnnouncements = ({ credentials }) => - promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) +const adminFetchAnnouncements = ({ credentials }) => { + return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials }) +} -export const dismissAnnouncement = ({ id, credentials }) => - promisedRequest({ +const fetchAnnouncements = ({ credentials }) => { + return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) +} + +const dismissAnnouncement = ({ id, credentials }) => { + return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id), credentials, method: 'POST', }) +} + +const adminListUsers = ({ opts, credentials }) => { + // the reported list is hardly useful because standards are for dating i guess, + // so make sure to fetchIfMissing right afterward using this call + const url = PLEROMA_ADMIN_USERS_URL_LIST(opts) + + return promisedRequest({ + url, + credentials, + method: 'GET', + }) +} + +const adminResendConfirmationEmail = ({ + screen_names: nicknames, + credentials, +}) => { + const url = PLEROMA_ADMIN_RESEND_CONFIRMATION_EMAIL_URL + return promisedRequest({ + url, + credentials, + method: 'PATCH', + payload: { + nicknames, + }, + }) +} + +const adminRequirePasswordChange = ({ + screen_names: nicknames, + credentials, +}) => { + const url = PLEROMA_ADMIN_REQUIRE_PASSWORD_CHANGE_URL + return promisedRequest({ + url, + credentials, + method: 'PATCH', + payload: { + nicknames, + }, + }) +} + +const adminDisableMFA = ({ screen_name: nickname, credentials }) => { + const url = PLEROMA_ADMIN_DISABLE_MFA_URL + return promisedRequest({ + url, + credentials, + method: 'PUT', + payload: { + nickname, + }, + }) +} + +const adminListStatuses = ({ opts, credentials }) => { + const url = PLEROMA_ADMIN_LIST_STATUSES_URL(opts) + + return promisedRequest({ + url, + credentials, + method: 'GET', + }) +} + +const adminChangeStatusScope = ({ + opts: { id, sensitive, visibility }, + credentials, +}) => { + const url = PLEROMA_ADMIN_CHANGE_STATUS_SCOPE_URL(id) + var payload = {} + if (typeof sensitive !== 'undefined') { + payload['sensitive'] = sensitive + } + if (typeof visibility !== 'undefined') { + payload['visibility'] = visibility + } + return promisedRequest({ + url, + credentials, + method: 'PUT', + payload, + }) +} + +const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => { + const payload = { content } + + if (typeof startsAt !== 'undefined') { + payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null + } + + if (typeof endsAt !== 'undefined') { + payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null + } + + if (typeof allDay !== 'undefined') { + payload.all_day = allDay + } + + return payload +} + +const postAnnouncement = ({ + credentials, + content, + startsAt, + endsAt, + allDay, +}) => { + return promisedRequest({ + url: PLEROMA_POST_ANNOUNCEMENT_URL, + credentials, + method: 'POST', + payload: announcementToPayload({ content, startsAt, endsAt, allDay }), + }) +} + +const editAnnouncement = ({ + id, + credentials, + content, + startsAt, + endsAt, + allDay, +}) => { + return promisedRequest({ + url: PLEROMA_EDIT_ANNOUNCEMENT_URL(id), + credentials, + method: 'PATCH', + payload: announcementToPayload({ content, startsAt, endsAt, allDay }), + }) +} + +const deleteAnnouncement = ({ id, credentials }) => { + return promisedRequest({ + url: PLEROMA_DELETE_ANNOUNCEMENT_URL(id), + credentials, + method: 'DELETE', + }) +} export const getMastodonSocketURI = ( { credentials, stream, args = {} }, @@ -1497,28 +2018,23 @@ export const WSConnectionStatus = Object.freeze({ STARTING_INITIAL: 6, }) -export const chats = ({ credentials }) => - promisedRequest({ - url: PLEROMA_CHATS_URL, - credentials, - }).then((data) => ({ - chatList: data.map(parseChat).filter((c) => c), - })) +const chats = ({ credentials }) => { + return fetch(PLEROMA_CHATS_URL, { headers: authHeaders(credentials) }) + .then((data) => data.json()) + .then((data) => { + return { chats: data.map(parseChat).filter((c) => c) } + }) +} -export const getOrCreateChat = ({ accountId, credentials }) => - promisedRequest({ +const getOrCreateChat = ({ accountId, credentials }) => { + return promisedRequest({ url: PLEROMA_CHAT_URL(accountId), method: 'POST', credentials, }) +} -export const chatMessages = ({ - id, - credentials, - maxId, - sinceId, - limit = 20, -}) => { +const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { let url = PLEROMA_CHAT_MESSAGES_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -1537,7 +2053,7 @@ export const chatMessages = ({ }) } -export const sendChatMessage = ({ +const sendChatMessage = ({ id, content, mediaId = null, @@ -1567,8 +2083,8 @@ export const sendChatMessage = ({ }) } -export const readChat = ({ id, lastReadId, credentials }) => - promisedRequest({ +const readChat = ({ id, lastReadId, credentials }) => { + return promisedRequest({ url: PLEROMA_CHAT_READ_URL(id), method: 'POST', payload: { @@ -1576,49 +2092,436 @@ export const readChat = ({ id, lastReadId, credentials }) => }, credentials, }) +} -export const deleteChatMessage = ({ chatId, messageId, credentials }) => - promisedRequest({ +const deleteChatMessage = ({ chatId, messageId, credentials }) => { + return promisedRequest({ url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId), method: 'DELETE', credentials, }) +} -export const fetchScrobbles = ({ accountId, limit = 1 }) => { +const setReportState = ({ id, state, credentials }) => { + // TODO: Can't use promisedRequest because on OK this does not return json + // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 + return fetch(PLEROMA_ADMIN_REPORTS, { + headers: { + ...authHeaders(credentials), + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + method: 'PATCH', + body: JSON.stringify({ + reports: [ + { + id, + state, + }, + ], + }), + }) + .then((data) => { + if (data.status >= 500) { + throw Error(data.statusText) + } else if (data.status >= 400) { + return data.json() + } + return data + }) + .then((data) => { + if (data.errors) { + throw Error(data.errors[0].message) + } + }) +} + +// ADMIN STUFF // EXPERIMENTAL +const fetchInstanceDBConfig = ({ credentials }) => { + return fetch(PLEROMA_ADMIN_CONFIG_URL, { + headers: authHeaders(credentials), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) +} + +const fetchInstanceConfigDescriptions = ({ credentials }) => { + return fetch(PLEROMA_ADMIN_DESCRIPTIONS_URL, { + headers: authHeaders(credentials), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) +} + +const fetchAvailableFrontends = ({ credentials }) => { + return fetch(PLEROMA_ADMIN_FRONTENDS_URL, { + headers: authHeaders(credentials), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) +} + +const pushInstanceDBConfig = ({ credentials, payload }) => { + return fetch(PLEROMA_ADMIN_CONFIG_URL, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...authHeaders(credentials), + }, + method: 'POST', + body: JSON.stringify(payload), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) +} + +const installFrontend = ({ credentials, payload }) => { + return fetch(PLEROMA_ADMIN_FRONTENDS_INSTALL_URL, { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...authHeaders(credentials), + }, + method: 'POST', + body: JSON.stringify(payload), + }).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) +} + +const fetchScrobbles = ({ accountId, limit = 1 }) => { let url = PLEROMA_SCROBBLES_URL(accountId) const params = [['limit', limit]] const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( '&', ) url += `?${queryString}` - return promisedRequest({ url }) + return fetch(url, {}).then((response) => { + if (response.ok) { + return response.json() + } else { + return { + error: response, + } + } + }) } -export const fetchBookmarkFolders = ({ credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDERS_URL, - credentials, - }) +const deleteEmojiPack = ({ name }) => { + return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'DELETE' }) +} -export const createBookmarkFolder = ({ name, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDERS_URL, - credentials, +const reloadEmoji = () => { + return fetch(PLEROMA_EMOJI_RELOAD_URL, { method: 'POST' }) +} + +const importEmojiFromFS = () => { + return fetch(PLEROMA_EMOJI_IMPORT_FS_URL) +} + +const createEmojiPack = ({ name }) => { + return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'POST' }) +} + +const listEmojiPacks = ({ page, pageSize }) => { + return fetch(PLEROMA_EMOJI_PACKS_URL(page, pageSize)) +} + +const listRemoteEmojiPacks = ({ instance, page, pageSize }) => { + if (!instance.startsWith('http')) { + instance = 'https://' + instance + } + + return fetch(PLEROMA_EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize), { + headers: { 'Content-Type': 'application/json' }, + }) +} + +const downloadRemoteEmojiPack = ({ instance, packName, as }) => { + return fetch(PLEROMA_EMOJI_PACKS_DL_REMOTE_URL, { method: 'POST', - payload: { name, emoji }, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: instance, + name: packName, + as, + }), }) +} -export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), - credentials, +const downloadRemoteEmojiPackZIP = ({ url, packName, file }) => { + const data = new FormData() + if (file) data.set('file', file) + if (url) data.set('url', url) + data.set('name', packName) + + return fetch(PLEROMA_EMOJI_PACKS_DL_REMOTE_ZIP_URL, { + method: 'POST', + body: data, + }) +} + +const saveEmojiPackMetadata = ({ name, newData }) => { + return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'PATCH', - payload: { name, emoji }, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ metadata: newData }), }) +} -export const deleteBookmarkFolder = ({ folderId, credentials }) => - promisedRequest({ - url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), - method: 'DELETE', - credentials, +const addNewEmojiFile = ({ packName, file, shortcode, filename }) => { + const data = new FormData() + if (filename.trim() !== '') { + data.set('filename', filename) + } + if (shortcode.trim() !== '') { + data.set('shortcode', shortcode) + } + data.set('file', file) + + return fetch(PLEROMA_EMOJI_UPDATE_FILE_URL(packName), { + method: 'POST', + body: data, }) +} + +const updateEmojiFile = ({ + packName, + shortcode, + newShortcode, + newFilename, + force, +}) => { + return fetch(PLEROMA_EMOJI_UPDATE_FILE_URL(packName), { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + shortcode, + new_shortcode: newShortcode, + new_filename: newFilename, + force, + }), + }) +} + +const deleteEmojiFile = ({ packName, shortcode }) => { + return fetch( + `${PLEROMA_EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`, + { method: 'DELETE' }, + ) +} + +const fetchBookmarkFolders = ({ credentials }) => { + const url = PLEROMA_BOOKMARK_FOLDERS_URL + return fetch(url, { headers: authHeaders(credentials) }).then((data) => + data.json(), + ) +} + +const createBookmarkFolder = ({ name, emoji, credentials }) => { + const url = PLEROMA_BOOKMARK_FOLDERS_URL + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(url, { + headers, + method: 'POST', + body: JSON.stringify({ name, emoji }), + }).then((data) => data.json()) +} + +const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => { + const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId) + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(url, { + headers, + method: 'PATCH', + body: JSON.stringify({ name, emoji }), + }).then((data) => data.json()) +} + +const deleteBookmarkFolder = ({ folderId, credentials }) => { + const url = PLEROMA_BOOKMARK_FOLDER_URL(folderId) + return fetch(url, { + method: 'DELETE', + headers: authHeaders(credentials), + }) +} + +const apiService = { + verifyCredentials, + fetchTimeline, + fetchPinnedStatuses, + fetchConversation, + fetchStatus, + fetchStatusSource, + fetchStatusHistory, + fetchFriends, + exportFriends, + fetchFollowers, + followUser, + unfollowUser, + pinOwnStatus, + unpinOwnStatus, + muteConversation, + unmuteConversation, + blockUser, + unblockUser, + removeUserFromFollowers, + editUserNote, + fetchUser, + fetchUserByName, + fetchUserRelationship, + favorite, + unfavorite, + retweet, + unretweet, + bookmarkStatus, + unbookmarkStatus, + postStatus, + editStatus, + deleteStatus, + uploadMedia, + setMediaDescription, + fetchMutes, + muteUser, + unmuteUser, + fetchBlocks, + fetchOAuthTokens, + revokeOAuthToken, + register, + getCaptcha, + updateProfileImages, + updateProfile, + updateProfileJSON, + importMutes, + importBlocks, + importFollows, + deleteAccount, + changeEmail, + moveAccount, + addAlias, + deleteAlias, + listAliases, + changePassword, + settingsMFA, + mfaDisableOTP, + generateMfaBackupCodes, + mfaSetupOTP, + mfaConfirmOTP, + addBackup, + listBackups, + fetchFollowRequests, + fetchLists, + createList, + getList, + updateList, + getListAccounts, + addAccountsToList, + removeAccountsFromList, + deleteList, + approveUser, + denyUser, + suggestions, + markNotificationsAsSeen, + dismissNotification, + vote, + fetchPoll, + fetchFavoritedByUsers, + fetchRebloggedByUsers, + fetchEmojiReactions, + reactWithEmoji, + unreactWithEmoji, + reportUser, + updateNotificationSettings, + search2, + searchUsers, + fetchKnownDomains, + fetchDomainMutes, + muteDomain, + unmuteDomain, + chats, + getOrCreateChat, + chatMessages, + sendChatMessage, + readChat, + deleteChatMessage, + setReportState, + fetchUserInLists, + fetchAnnouncements, + dismissAnnouncement, + postAnnouncement, + editAnnouncement, + deleteAnnouncement, + fetchScrobbles, + adminFetchAnnouncements, + fetchInstanceDBConfig, + fetchInstanceConfigDescriptions, + fetchAvailableFrontends, + pushInstanceDBConfig, + installFrontend, + importEmojiFromFS, + reloadEmoji, + listEmojiPacks, + createEmojiPack, + deleteEmojiPack, + saveEmojiPackMetadata, + addNewEmojiFile, + updateEmojiFile, + deleteEmojiFile, + listRemoteEmojiPacks, + downloadRemoteEmojiPack, + downloadRemoteEmojiPackZIP, + fetchBookmarkFolders, + createBookmarkFolder, + updateBookmarkFolder, + deleteBookmarkFolder, + adminListUsers, + adminGetUserData, + adminResendConfirmationEmail, + adminDeleteAccounts, + adminSetUsersRight, + adminSetUsersTags, + adminSetUsersApprovalStatus, + adminSetUsersConfirmationStatus, + adminSetUsersActivationStatus, + adminSetUsersSuggestionStatus, + adminListStatuses, + adminChangeStatusScope, + adminRequirePasswordChange, + adminDisableMFA, +} + +export default apiService diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js deleted file mode 100644 index 80012cca3..000000000 --- a/src/services/api/helpers.js +++ /dev/null @@ -1,87 +0,0 @@ -import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' - -export const promisedRequest = ({ - method, - url, - params, - payload, - formData, - credentials, - headers = {}, -}) => { - const options = { - method, - credentials: 'same-origin', - headers: { - Accept: 'application/json', - ...headers, - }, - } - if (!formData) { - options.headers['Content-Type'] = 'application/json' - } - if (params) { - url += - '?' + - Object.entries(params) - .map( - ([key, value]) => - encodeURIComponent(key) + '=' + encodeURIComponent(value), - ) - .join('&') - } - if (formData || payload) { - options.body = formData || JSON.stringify(payload) - } - - if (credentials) { - options.headers = { - ...options.headers, - ...authHeaders(credentials), - } - } - - return fetch(url, options).then((response) => { - return new Promise((resolve, reject) => { - // 204 is "No content", which fails to parse json (as you'd might think) - if (response.ok && response.status === 204) resolve() - - return response - .json() - .then((json) => { - if (!response.ok) { - return reject( - new StatusCodeError( - response.status, - json, - { url, options }, - response, - ), - ) - } - - json._response = response - - return resolve(json) - }) - .catch((error) => { - return reject( - new StatusCodeError( - response.status, - error, - { url, options }, - response, - ), - ) - }) - }) - }) -} - -const authHeaders = (accessToken) => { - if (accessToken) { - return { Authorization: `Bearer ${accessToken}` } - } else { - return {} - } -} diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js new file mode 100644 index 000000000..adc18ef7f --- /dev/null +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -0,0 +1,75 @@ +import bookmarkFoldersFetcher from '../../services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js' +import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service' +import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js' +import apiService, { + getMastodonSocketURI, + ProcessedWS, +} from '../api/api.service.js' +import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js' +import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js' + +import { useInstanceStore } from 'src/stores/instance.js' + +const backendInteractorService = (credentials) => ({ + startFetchingTimeline({ + timeline, + store, + userId = false, + listId = false, + statusId = false, + bookmarkFolderId = false, + tag, + }) { + return timelineFetcher.startFetching({ + timeline, + store, + credentials, + userId, + listId, + statusId, + bookmarkFolderId, + tag, + }) + }, + + fetchTimeline(args) { + return timelineFetcher.fetchAndUpdate({ ...args, credentials }) + }, + + startFetchingNotifications({ store }) { + return notificationsFetcher.startFetching({ store, credentials }) + }, + + fetchNotifications(args) { + return notificationsFetcher.fetchAndUpdate({ ...args, credentials }) + }, + + startFetchingFollowRequests({ store }) { + return followRequestFetcher.startFetching({ store, credentials }) + }, + + startFetchingLists({ store }) { + return listsFetcher.startFetching({ store, credentials }) + }, + + startFetchingBookmarkFolders({ store }) { + return bookmarkFoldersFetcher.startFetching({ store, credentials }) + }, + + startUserSocket({ store }) { + const serv = useInstanceStore().server.replace('http', 'ws') + const url = getMastodonSocketURI({}, serv) + return ProcessedWS({ url, id: 'Unified', credentials }) + }, + + ...Object.entries(apiService).reduce((acc, [key, func]) => { + return { + ...acc, + [key]: (args) => func({ credentials, ...args }), + } + }, {}), + + verifyCredentials: apiService.verifyCredentials, +}) + +export default backendInteractorService diff --git a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js new file mode 100644 index 000000000..7b81c19dc --- /dev/null +++ b/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js @@ -0,0 +1,32 @@ +import apiService from '../api/api.service.js' +import { promiseInterval } from '../promise_interval/promise_interval.js' + +import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' + +const fetchAndUpdate = ({ credentials }) => { + return apiService + .fetchBookmarkFolders({ credentials }) + .then( + (bookmarkFolders) => { + useBookmarkFoldersStore().setBookmarkFolders(bookmarkFolders) + }, + (rej) => { + console.error(rej) + }, + ) + .catch((e) => { + console.error(e) + }) +} + +const startFetching = ({ credentials, store }) => { + const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store }) + boundFetchAndUpdate() + return promiseInterval(boundFetchAndUpdate, 240000) +} + +const bookmarkFoldersFetcher = { + startFetching, +} + +export default bookmarkFoldersFetcher diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index c754c7316..209a5f0c0 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -1,18 +1,8 @@ -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - fetchUserRelationship, - followUser, - unfollowUser, -} from 'src/services/api/api.service.js' - const fetchRelationship = (attempt, userId, store) => new Promise((resolve, reject) => { setTimeout(() => { - fetchUserRelationship({ - id: userId, - credentials: useOAuthStore().token, - }) + store.state.api.backendInteractor + .fetchUserRelationship({ id: userId }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) return relationship @@ -37,40 +27,38 @@ const fetchRelationship = (attempt, userId, store) => export const requestFollow = (userId, store) => new Promise((resolve) => { - followUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) + store.state.api.backendInteractor + .followUser({ id: userId }) + .then((updated) => { + store.commit('updateUserRelationship', [updated]) - if (updated.following || (updated.locked && updated.requested)) { - // If we get result immediately or the account is locked, just stop. - resolve() - return - } + if (updated.following || (updated.locked && updated.requested)) { + // If we get result immediately or the account is locked, just stop. + resolve() + return + } - // But usually we don't get result immediately, so we ask server - // for updated user profile to confirm if we are following them - // Sometimes it takes several tries. Sometimes we end up not following - // user anyway, probably because they locked themselves and we - // don't know that yet. - // Recursive Promise, it will call itself up to 3 times. + // But usually we don't get result immediately, so we ask server + // for updated user profile to confirm if we are following them + // Sometimes it takes several tries. Sometimes we end up not following + // user anyway, probably because they locked themselves and we + // don't know that yet. + // Recursive Promise, it will call itself up to 3 times. - return fetchRelationship(1, updated, store).then(() => { - resolve() + return fetchRelationship(1, updated, store).then(() => { + resolve() + }) }) - }) }) export const requestUnfollow = (userId, store) => new Promise((resolve) => { - unfollowUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) - resolve({ - updated, + store.state.api.backendInteractor + .unfollowUser({ id: userId }) + .then((updated) => { + store.commit('updateUserRelationship', [updated]) + resolve({ + updated, + }) }) - }) }) diff --git a/src/services/follow_request_fetcher/follow_request_fetcher.service.js b/src/services/follow_request_fetcher/follow_request_fetcher.service.js index 015eb55ed..530c98aa7 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -1,8 +1,9 @@ -import { fetchFollowRequests } from '../api/api.service.js' +import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' const fetchAndUpdate = ({ store, credentials }) => { - return fetchFollowRequests({ credentials }) + return apiService + .fetchFollowRequests({ credentials }) .then( (requests) => { store.commit('setFollowRequests', requests) diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js new file mode 100644 index 000000000..c395ef93b --- /dev/null +++ b/src/services/lists_fetcher/lists_fetcher.service.js @@ -0,0 +1,32 @@ +import apiService from '../api/api.service.js' +import { promiseInterval } from '../promise_interval/promise_interval.js' + +import { useListsStore } from 'src/stores/lists.js' + +const fetchAndUpdate = ({ credentials }) => { + return apiService + .fetchLists({ credentials }) + .then( + (lists) => { + useListsStore().setLists(lists) + }, + (rej) => { + console.error(rej) + }, + ) + .catch((e) => { + console.error(e) + }) +} + +const startFetching = ({ credentials, store }) => { + const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store }) + boundFetchAndUpdate() + return promiseInterval(boundFetchAndUpdate, 240000) +} + +const listsFetcher = { + startFetching, +} + +export default listsFetcher diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index ec6c47a5d..c1a9e1a2f 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,4 @@ -import { fetchTimeline } from '../api/api.service.js' +import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -80,7 +80,8 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { } const fetchNotifications = ({ store, args, older }) => { - return fetchTimeline(args) + return apiService + .fetchTimeline(args) .then((response) => { if (response.errors) { if ( diff --git a/src/services/promise_interval/promise_interval.js b/src/services/promise_interval/promise_interval.js index b28b7dc5a..46ac68996 100644 --- a/src/services/promise_interval/promise_interval.js +++ b/src/services/promise_interval/promise_interval.js @@ -4,39 +4,32 @@ // time after the first interval. // - interval is the interval delay in ms. -const wait = (timeout) => { - let timeoutId - const promise = () => - new Promise((resolve) => { - timeoutId = window.setTimeout(() => resolve(), timeout) - }) - return { timeoutId, promise } -} - export const promiseInterval = (promiseCall, interval) => { let stopped = false let timeout = null + const func = () => { + const promise = promiseCall() + // something unexpected happened and promiseCall did not + // return a promise, abort the loop. + if (!(promise && promise.finally)) { + console.warn( + 'promiseInterval: promise call did not return a promise, stopping interval.', + ) + return + } + promise.finally(() => { + if (stopped) return + timeout = window.setTimeout(func, interval) + }) + } + const stopFetcher = () => { stopped = true window.clearTimeout(timeout) } - const loop = new Promise(async (resolve, reject) => { - try { - while (!stopped) { - await promiseCall() - const { timeoutId, promise } = wait(interval) - timeout = timeoutId - await promise() - } - resolve() - } catch (e) { - reject(e) - } - }) - - loop.then() + timeout = window.setTimeout(func, interval) return { stop: stopFetcher } } diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 1a814ec7c..021c31ef8 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,11 +1,6 @@ import { map } from 'lodash' -import { - editStatus as apiEditStatus, - postStatus as apiPostStatus, - setMediaDescription as apiSetMediaDescription, - uploadMedia as apiUploadMedia, -} from '../api/api.service.js' +import apiService from '../api/api.service.js' const postStatus = ({ store, @@ -23,20 +18,21 @@ const postStatus = ({ }) => { const mediaIds = map(media, 'id') - return apiPostStatus({ - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - visibility, - sensitive, - mediaIds, - inReplyToStatusId, - quoteId, - contentType, - poll, - preview, - idempotencyKey, - }) + return apiService + .postStatus({ + credentials: store.state.users.currentUser.credentials, + status, + spoilerText, + visibility, + sensitive, + mediaIds, + inReplyToStatusId, + quoteId, + contentType, + poll, + preview, + idempotencyKey, + }) .then((data) => { if (!data.error && !preview) { store.dispatch('addNewStatuses', { @@ -67,16 +63,17 @@ const editStatus = ({ }) => { const mediaIds = map(media, 'id') - return editStatus({ - id: statusId, - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - sensitive, - poll, - mediaIds, - contentType, - }) + return apiService + .editStatus({ + id: statusId, + credentials: store.state.users.currentUser.credentials, + status, + spoilerText, + sensitive, + poll, + mediaIds, + contentType, + }) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { @@ -98,12 +95,12 @@ const editStatus = ({ const uploadMedia = ({ store, formData }) => { const credentials = store.state.users.currentUser.credentials - return apiUploadMedia({ credentials, formData }) + return apiService.uploadMedia({ credentials, formData }) } const setMediaDescription = ({ store, id, description }) => { const credentials = store.state.users.currentUser.credentials - return apiSetMediaDescription({ credentials, id, description }) + return apiService.setMediaDescription({ credentials, id, description }) } const statusPosterService = { diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 8e9b8a2e9..68991addf 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -1,6 +1,6 @@ import { camelCase } from 'lodash' -import { fetchTimeline } from '../api/api.service.js' +import apiService from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -75,7 +75,8 @@ const fetchAndUpdate = ({ const numStatusesBeforeFetch = timelineData.statuses.length - return fetchTimeline(args) + return apiService + .fetchTimeline(args) .then((response) => { if (response.errors) { if (timeline === 'favorites') { diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 884ac37a1..b07315034 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -1,38 +1,6 @@ import { cloneDeep, differenceWith, flatten, get, isEqual, set } from 'lodash' import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - addNewEmojiFile, - changeStatusScope, - createEmojiPack, - deleteAccounts, - deleteEmojiPack, - disableMFA, - downloadRemoteEmojiPack, - downloadRemoteEmojiPackZIP, - getAvailableFrontends, - getInstanceConfigDescriptions, - getInstanceDBConfig, - getUserData, - importEmojiFromFS, - installFrontend, - listRemoteEmojiPacks, - listStatuses, - listUsers, - pushInstanceDBConfig, - reloadEmoji, - requirePasswordChange, - resendConfirmationEmail, - setUsersActivationStatus, - setUsersApprovalStatus, - setUsersConfirmationStatus, - setUsersRight, - setUsersSuggestionStatus, - setUsersTags, -} from 'src/services/api/admin.js' -import { listEmojiPacks } from 'src/services/api/api.service.js' import { parseStatus } from 'src/services/entity_normalizer/entity_normalizer.service.js' export const defaultState = { @@ -53,6 +21,7 @@ export const newUserFlags = { export const useAdminSettingsStore = defineStore('adminSettings', { state: () => ({ ...cloneDeep(defaultState), + backendInteractor: window.vuex.state.api.backendInteractor, }), actions: { // Configuration Stuff @@ -85,9 +54,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, loadAdminStuff() { - getInstanceDBConfig({ - credentials: useOAuthStore().token, - }).then((backendDbConfig) => { + this.backendInteractor.fetchInstanceDBConfig().then((backendDbConfig) => { if (backendDbConfig.error) { if (backendDbConfig.error.status === 400) { backendDbConfig.error.json().then((errorJson) => { @@ -97,21 +64,15 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) } } else { - this.setInstanceAdminSettings({ - credentials: useOAuthStore().token, - backendDbConfig, - }) + this.setInstanceAdminSettings({ backendDbConfig }) } }) if (this.descriptions === null) { - getInstanceConfigDescriptions({ - credentials: useOAuthStore().token, - }).then((backendDescriptions) => - this.setInstanceAdminDescriptions({ - credentials: useOAuthStore().token, - backendDescriptions, - }), - ) + this.backendInteractor + .fetchInstanceConfigDescriptions() + .then((backendDescriptions) => + this.setInstanceAdminDescriptions({ backendDescriptions }), + ) } }, setInstanceAdminSettings({ backendDbConfig }) { @@ -242,23 +203,17 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }) - pushInstanceDBConfig({ - credentials: useOAuthStore().token, - payload: { - configs: changed, - }, - }) + window.vuex.state.api.backendInteractor + .pushInstanceDBConfig({ + payload: { + configs: changed, + }, + }) .then(() => - getInstanceDBConfig({ - credentials: useOAuthStore().token, - }), + window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ - credentials: useOAuthStore().token, - - backendDbConfig, - }), + this.setInstanceAdminSettings({ backendDbConfig }), ) }, pushAdminSetting({ path, value }) { @@ -279,28 +234,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } } - pushInstanceDBConfig({ - credentials: useOAuthStore().token, - payload: { - configs: [ - { - group, - key, - value: convert(clone), - }, - ], - }, - }) + window.vuex.state.api.backendInteractor + .pushInstanceDBConfig({ + payload: { + configs: [ + { + group, + key, + value: convert(clone), + }, + ], + }, + }) .then(() => - getInstanceDBConfig({ - credentials: useOAuthStore().token, - }), + window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ - credentials: useOAuthStore().token, - backendDbConfig, - }), + this.setInstanceAdminSettings({ backendDbConfig }), ) }, resetAdminSetting({ path }) { @@ -310,23 +260,21 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.modifiedPaths.delete(path) - return pushInstanceDBConfig({ - credentials: useOAuthStore().token, - payload: { - configs: [ - { - group, - key, - delete: true, - subkeys: [subkey], - }, - ], - }, - }) + return window.vuex.state.api.backendInteractor + .pushInstanceDBConfig({ + payload: { + configs: [ + { + group, + key, + delete: true, + subkeys: [subkey], + }, + ], + }, + }) .then(() => - getInstanceDBConfig({ - credentials: useOAuthStore().token, - }), + window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), @@ -335,9 +283,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Frontends Stuff loadFrontendsStuff() { - getAvailableFrontends({ - credentials: useOAuthStore().token, - }).then((frontends) => this.setAvailableFrontends({ frontends })) + this.backendInteractor + .fetchAvailableFrontends() + .then((frontends) => this.setAvailableFrontends({ frontends })) }, setAvailableFrontends({ frontends }) { @@ -352,18 +300,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) }, - installFrontend() { - return installFrontend({ - credentials: useOAuthStore().token, - }) - }, - // Statuses stuff async fetchStatuses(opts) { - const { total, activities } = await listStatuses({ - credentials: useOAuthStore().token, - opts, - }) + const { total, activities } = + await this.backendInteractor.adminListStatuses({ + opts, + }) const statuses = activities.map(parseStatus) @@ -375,8 +317,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async changeStatusScope(opts) { - const raw = await changeStatusScope({ - credentials: useOAuthStore().token, + const raw = await this.backendInteractor.adminChangeStatusScope({ opts, }) const status = parseStatus(raw) @@ -386,9 +327,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Users stuff async fetchUsers(opts) { - const { users, count } = await listUsers({ - credentials: useOAuthStore().token, - + const { users, count } = await this.backendInteractor.adminListUsers({ opts, }) @@ -405,23 +344,17 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async getUserData({ user }) { - const api = getUserData + const api = this.backendInteractor.adminGetUserData const { screen_name } = user - const result = await api({ - credentials: useOAuthStore().token, - screen_name, - }) + const result = await api({ screen_name }) window.vuex.commit('updateUserAdminData', { user: result }) }, async deleteUsers({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = deleteAccounts + const api = this.backendInteractor.adminDeleteAccounts - const resultUserIds = await api({ - credentials: useOAuthStore().token, - screen_names, - }) + const resultUserIds = await api({ screen_names }) resultUserIds.forEach((userId) => { window.vuex.dispatch( @@ -436,16 +369,14 @@ export const useAdminSettingsStore = defineStore('adminSettings', { resendConfirmationEmail({ users }) { const screen_names = users.map((u) => u.screen_name) - return resendConfirmationEmail({ - credentials: useOAuthStore().token, + return this.backendInteractor.adminResendConfirmationEmail({ screen_names, }) }, requirePasswordChange({ users }) { const screen_names = users.map((u) => u.screen_name) - return requirePasswordChange({ - credentials: useOAuthStore().token, + return this.backendInteractor.adminRequirePasswordChange({ screen_names, }) }, @@ -453,17 +384,13 @@ export const useAdminSettingsStore = defineStore('adminSettings', { disableMFA({ user }) { const { screen_name } = user - return disableMFA({ - credentials: useOAuthStore().token, - screen_name, - }) + return this.backendInteractor.adminDisableMFA({ screen_name }) }, async setUsersTags({ users, tags, value }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersTags + const api = this.backendInteractor.adminSetUsersTags await api({ - credentials: useOAuthStore().token, screen_names, tags, value, @@ -475,10 +402,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersRight({ users, right, value }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersRight + const api = this.backendInteractor.adminSetUsersRight await api({ - credentials: useOAuthStore().token, screen_names, right, value, @@ -490,10 +416,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersActivationStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersActivationStatus + const api = this.backendInteractor.adminSetUsersActivationStatus const resultUsers = await api({ - credentials: useOAuthStore().token, screen_names, value, }) @@ -504,10 +429,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersSuggestionStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersSuggestionStatus + const api = this.backendInteractor.adminSetUsersSuggestionStatus const resultUsers = await api({ - credentials: useOAuthStore().token, screen_names, value, }) @@ -518,12 +442,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersConfirmationStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersConfirmationStatus + const api = this.backendInteractor.adminSetUsersConfirmationStatus - await api({ - credentials: useOAuthStore().token, - screen_names, - }) + await api({ screen_names }) users.forEach((user) => { this.getUserData({ user }) @@ -531,10 +452,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersApprovalStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = setUsersApprovalStatus + const api = this.backendInteractor.adminSetUsersApprovalStatus const resultUsers = await api({ - credentials: useOAuthStore().token, screen_names, }) @@ -542,66 +462,5 @@ export const useAdminSettingsStore = defineStore('adminSettings', { window.vuex.commit('updateUserAdminData', { user }) }) }, - reloadEmoji() { - return reloadEmoji({ credentials: useOAuthStore().token }) - }, - importEmojiFromFS() { - return importEmojiFromFS({ credentials: useOAuthStore().token }) - }, - listEmojiPacks(params) { - return listEmojiPacks({ - ...params, - credentials: useOAuthStore().token, - }) - }, - listRemoteEmojiPacks(params) { - return listRemoteEmojiPacks({ - ...params, - credentials: useOAuthStore().token, - }) - }, - addNewEmojiFile({ packName, file, shortcode, filename }) { - return addNewEmojiFile({ - packName, - file, - shortcode, - filename, - credentials: useOAuthStore().token, - }) - }, - downloadRemoteEmojiPack({ instance, packName, as }) { - return downloadRemoteEmojiPack({ - instance, - packName, - as, - credentials: useOAuthStore().token, - }) - }, - downloadRemoteEmojiPackZIP({ url, packName }) { - return downloadRemoteEmojiPackZIP({ - url, - packName, - credentials: useOAuthStore().token, - }) - }, - createEmojiPack({ name }) { - return createEmojiPack({ - name, - credentials: useOAuthStore().token, - }) - }, - deleteEmojiPack({ name }) { - return createEmojiPack({ - name, - credentials: useOAuthStore().token, - }) - }, - saveEmojiPackMetadata({ name, newData }) { - return createEmojiPack({ - name, - newData, - credentials: useOAuthStore().token, - }) - }, }, }) diff --git a/src/stores/announcements.js b/src/stores/announcements.js index 87d83813a..cb325dadd 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -1,18 +1,5 @@ import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - getAnnouncements as adminGetAnnouncements, - deleteAnnouncement, - editAnnouncement, - postAnnouncement, -} from 'src/services/api/admin.js' -import { - dismissAnnouncement, - getAnnouncements, -} from 'src/services/api/api.service.js' - const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5 export const useAnnouncementsStore = defineStore('announcements', { @@ -44,19 +31,15 @@ export const useAnnouncementsStore = defineStore('announcements', { currentUser && currentUser.privileges.has('announcements_manage_announcements') - const fetchAnnouncements = async () => { + const getAnnouncements = async () => { if (!isAdmin) { - return getAnnouncements({ - credentials: useOAuthStore().token, - }) + return window.vuex.state.api.backendInteractor.fetchAnnouncements() } - const all = await adminGetAnnouncements({ - credentials: useOAuthStore().token, - }) - const visible = await getAnnouncements({ - credentials: useOAuthStore().token, - }) + const all = + await window.vuex.state.api.backendInteractor.adminFetchAnnouncements() + const visible = + await window.vuex.state.api.backendInteractor.fetchAnnouncements() const visibleObject = visible.reduce((a, c) => { a[c.id] = c return a @@ -76,7 +59,7 @@ export const useAnnouncementsStore = defineStore('announcements', { return all } - return fetchAnnouncements() + return getAnnouncements() .then((announcements) => { this.announcements = announcements }) @@ -91,18 +74,17 @@ export const useAnnouncementsStore = defineStore('announcements', { }) }, markAnnouncementAsRead(id) { - return dismissAnnouncement({ - id, - credentials: useOAuthStore().token, - }).then(() => { - const index = this.announcements.findIndex((a) => a.id === id) + return window.vuex.state.api.backendInteractor + .dismissAnnouncement({ id }) + .then(() => { + const index = this.announcements.findIndex((a) => a.id === id) - if (index < 0) { - return - } + if (index < 0) { + return + } - this.announcements[index].read = true - }) + this.announcements[index].read = true + }) }, startFetchingAnnouncements() { if (this.fetchAnnouncementsTimer) { @@ -123,35 +105,25 @@ export const useAnnouncementsStore = defineStore('announcements', { clearInterval(interval) }, postAnnouncement({ content, startsAt, endsAt, allDay }) { - return postAnnouncement({ - credentials: useOAuthStore().token, - content, - startsAt, - endsAt, - allDay, - }).then(() => { - return this.fetchAnnouncements() - }) + return window.vuex.state.api.backendInteractor + .postAnnouncement({ content, startsAt, endsAt, allDay }) + .then(() => { + return this.fetchAnnouncements() + }) }, editAnnouncement({ id, content, startsAt, endsAt, allDay }) { - return editAnnouncement({ - id, - content, - startsAt, - endsAt, - allDay, - credentials: useOAuthStore().token, - }).then(() => { - return this.fetchAnnouncements() - }) + return window.vuex.state.api.backendInteractor + .editAnnouncement({ id, content, startsAt, endsAt, allDay }) + .then(() => { + return this.fetchAnnouncements() + }) }, deleteAnnouncement(id) { - return deleteAnnouncement({ - id, - credentials: useOAuthStore().token, - }).then(() => { - return this.fetchAnnouncements() - }) + return window.vuex.state.api.backendInteractor + .deleteAnnouncement({ id }) + .then(() => { + return this.fetchAnnouncements() + }) }, }, }) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 3e16121b1..028322f9d 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -1,16 +1,6 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - createBookmarkFolder, - deleteBookmarkFolder, - fetchBookmarkFolders, - updateBookmarkFolder, -} from 'src/services/api/api.service.js' -import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' - export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { state: () => ({ allFolders: [], @@ -26,23 +16,6 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { }, }, actions: { - startFetching() { - promiseInterval(() => { - this.fetcher = fetchBookmarkFolders({ - credentials: useOAuthStore().token, - }) - .then( - (folders) => this.setBookmarkFolders(folders), - (rej) => console.error(rej), - ) - .catch((e) => { - console.error(e) - }) - }, 240000) - }, - stopFetching() { - this.fetcher?.stop() - }, setBookmarkFolders(value) { this.allFolders = value }, @@ -57,31 +30,23 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { } }, createBookmarkFolder({ name, emoji }) { - return createBookmarkFolder({ - name, - emoji, - credentials: useOAuthStore().token, - }).then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return window.vuex.state.api.backendInteractor + .createBookmarkFolder({ name, emoji }) + .then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, updateBookmarkFolder({ folderId, name, emoji }) { - return updateBookmarkFolder({ - credentials: useOAuthStore().token, - folderId, - name, - emoji, - }).then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return window.vuex.state.api.backendInteractor + .updateBookmarkFolder({ folderId, name, emoji }) + .then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, deleteBookmarkFolder({ folderId }) { - deleteBookmarkFolder({ - folderId, - credentials: useOAuthStore().token, - }) + window.vuex.state.api.backendInteractor.deleteBookmarkFolder({ folderId }) remove(this.allFolders, (folder) => folder.id === folderId) }, }, diff --git a/src/stores/emoji.js b/src/stores/emoji.js index 079dedb9f..e28143300 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -2,10 +2,8 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' import { useInstanceStore } from 'src/stores/instance.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { ensureFinalFallback } from 'src/i18n/languages.js' -import { listEmojiPacks } from 'src/services/api/api.service.js' import { annotationsLoader } from 'virtual:pleroma-fe/emoji-annotations' @@ -185,14 +183,13 @@ export const useEmojiStore = defineStore('emoji', { async getAdminPacksLocal(refresh) { if (!refresh && this.adminPacksLocal) return this.adminPacksLocal + const backendInteractor = window.vuex.state.api.backendInteractor + const listFunction = backendInteractor.listEmojiPacks + this.adminPacksLocalLoading = true this.adminPacksLocal = await this.getAdminPacks( useInstanceStore().server, - (params) => - listEmojiPacks({ - ...params, - credentials: useOAuthStore().token, - }), + listFunction, ) this.adminPacksLocalLoading = false }, @@ -209,6 +206,7 @@ export const useEmojiStore = defineStore('emoji', { page: 1, pageSize: 0, }) + .then((data) => data.json()) .then((data) => { if (data.error !== undefined) { return Promise.reject(data.error) @@ -222,13 +220,15 @@ export const useEmojiStore = defineStore('emoji', { instance, page: i, pageSize, - }).then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } + }) + .then((data) => data.json()) + .then((pageData) => { + if (pageData.error !== undefined) { + return Promise.reject(pageData.error) + } - return pageData.packs - }), + return pageData.packs + }), ) } @@ -247,7 +247,7 @@ export const useEmojiStore = defineStore('emoji', { }, {}) }) .catch((data) => { - console.error(data) + this.displayError(data) }) }, diff --git a/src/stores/instance.js b/src/stores/instance.js index 2f9bf3c44..54b3cf43c 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -11,7 +11,7 @@ import { LOCAL_DEFAULT_CONFIG_DEFINITIONS, validateSetting, } from '../modules/default_config_state.js' -import { fetchKnownDomains } from '../services/api/api.service.js' +import apiService from '../services/api/api.service.js' import { useInterfaceStore } from 'src/stores/interface.js' @@ -210,7 +210,7 @@ export const useInstanceStore = defineStore('instance', { }, async getKnownDomains() { try { - this.knownDomains = await fetchKnownDomains({ + this.knownDomains = await apiService.fetchKnownDomains({ credentials: window.vuex.state.users.currentUser.credentials, }) } catch (e) { diff --git a/src/stores/lists.js b/src/stores/lists.js index c361b6e5e..b33a119cc 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -1,23 +1,8 @@ import { find, remove } from 'lodash' import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - addAccountsToList, - createList, - deleteList, - fetchLists, - getList, - getListAccounts, - removeAccountsFromList, - updateList, -} from 'src/services/api/api.service.js' -import { promiseInterval } from 'src/services/promise_interval/promise_interval.js' - export const useListsStore = defineStore('lists', { state: () => ({ - fetcher: null, allLists: [], allListsObject: {}, }), @@ -33,58 +18,34 @@ export const useListsStore = defineStore('lists', { }, }, actions: { - startFetching() { - promiseInterval(() => { - this.fetcher = fetchLists({ - credentials: useOAuthStore().token, - }) - .then( - (lists) => this.setLists(lists), - (rej) => console.error(rej), - ) - .catch((e) => { - console.error(e) - }) - }, 240000) - }, - stopFetching() { - this.fetcher?.stop() - }, setLists(value) { this.allLists = value }, createList({ title }) { - return createList({ - title, - credentials: useOAuthStore().token, - }).then((list) => { - this.setList({ listId: list.id, title }) - return list - }) + return window.vuex.state.api.backendInteractor + .createList({ title }) + .then((list) => { + this.setList({ listId: list.id, title }) + return list + }) }, fetchList({ listId }) { - return getList({ - listId, - credentials: useOAuthStore().token, - }).then((list) => this.setList({ listId: list.id, title: list.title })) + return window.vuex.state.api.backendInteractor + .getList({ listId }) + .then((list) => this.setList({ listId: list.id, title: list.title })) }, fetchListAccounts({ listId }) { - return getListAccounts({ - listId, - credentials: useOAuthStore().token, - }).then((accountIds) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - this.allListsObject[listId].accountIds = accountIds - }) + return window.vuex.state.api.backendInteractor + .getListAccounts({ listId }) + .then((accountIds) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + this.allListsObject[listId].accountIds = accountIds + }) }, setList({ listId, title }) { - updateList({ - listId, - title, - credentials: useOAuthStore().token, - }) + window.vuex.state.api.backendInteractor.updateList({ listId, title }) if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -107,55 +68,46 @@ export const useListsStore = defineStore('lists', { } this.allListsObject[listId].accountIds = accountIds if (added.length > 0) { - addAccountsToList({ + window.vuex.state.api.backendInteractor.addAccountsToList({ listId, accountIds: added, - credentials: useOAuthStore().token, }) } if (removed.length > 0) { - removeAccountsFromList({ + window.vuex.state.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed, - credentials: useOAuthStore().token, }) } }, addListAccount({ listId, accountId }) { - return addAccountsToList({ - listId, - accountIds: [accountId], - credentials: useOAuthStore().token, - }).then((result) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - this.allListsObject[listId].accountIds.push(accountId) - return result - }) + return window.vuex.state.api.backendInteractor + .addAccountsToList({ listId, accountIds: [accountId] }) + .then((result) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + this.allListsObject[listId].accountIds.push(accountId) + return result + }) }, removeListAccount({ listId, accountId }) { - return removeAccountsFromList({ - listId, - accountIds: [accountId], - credentials: useOAuthStore().token, - }).then((result) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - const { accountIds } = this.allListsObject[listId] - const set = new Set(accountIds) - set.delete(accountId) - this.allListsObject[listId].accountIds = [...set] + return window.vuex.state.api.backendInteractor + .removeAccountsFromList({ listId, accountIds: [accountId] }) + .then((result) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + const { accountIds } = this.allListsObject[listId] + const set = new Set(accountIds) + set.delete(accountId) + this.allListsObject[listId].accountIds = [...set] - return result - }) + return result + }) }, deleteList({ listId }) { - deleteList({ - listId, - credentials: useOAuthStore().token, - }) + window.vuex.state.api.backendInteractor.deleteList({ listId }) delete this.allListsObject[listId] remove(this.allLists, (list) => list.id === listId) diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 116169b81..2a79c2fa9 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -41,9 +41,12 @@ export const useOAuthStore = defineStore('oauth', { userToken: false, }), getters: { - token() { + getToken() { return this.userToken || this.appToken }, + getUserToken() { + return this.userToken + }, }, actions: { setClientData({ clientId, clientSecret }) { diff --git a/src/stores/oauth_tokens.js b/src/stores/oauth_tokens.js index 3f9138cec..ae9b396ac 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -1,33 +1,25 @@ import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { - fetchOAuthTokens, - revokeOAuthToken, -} from 'src/services/api/api.service.js' - export const useOAuthTokensStore = defineStore('oauthTokens', { state: () => ({ tokens: [], }), actions: { fetchTokens() { - fetchOAuthTokens({ - credentials: useOAuthStore().token, - }).then((tokens) => { - this.swapTokens(tokens) - }) + window.vuex.state.api.backendInteractor + .fetchOAuthTokens() + .then((tokens) => { + this.swapTokens(tokens) + }) }, revokeToken(id) { - revokeOAuthToken({ - id, - credentials: useOAuthStore().token, - }).then((response) => { - if (response.status === 201) { - this.swapTokens(this.tokens.filter((token) => token.id !== id)) - } - }) + window.vuex.state.api.backendInteractor + .revokeOAuthToken({ id }) + .then((response) => { + if (response.status === 201) { + this.swapTokens(this.tokens.filter((token) => token.id !== id)) + } + }) }, swapTokens(tokens) { this.tokens = tokens diff --git a/src/stores/polls.js b/src/stores/polls.js index 78ccd059b..aac8a2421 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -1,10 +1,6 @@ import { merge } from 'lodash' import { defineStore } from 'pinia' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { fetchPoll, vote } from 'src/services/api/api.service.js' - export const usePollsStore = defineStore('polls', { state: () => ({ // Contains key = id, value = number of trackers for this poll @@ -23,17 +19,16 @@ export const usePollsStore = defineStore('polls', { } }, updateTrackedPoll(pollId) { - fetchPoll({ - pollId, - credentials: useOAuthStore().token, - }).then((poll) => { - setTimeout(() => { - if (this.trackedPolls[pollId]) { - this.updateTrackedPoll(pollId) - } - }, 30 * 1000) - this.mergeOrAddPoll(poll) - }) + window.vuex.state.api.backendInteractor + .fetchPoll({ pollId }) + .then((poll) => { + setTimeout(() => { + if (this.trackedPolls[pollId]) { + this.updateTrackedPoll(pollId) + } + }, 30 * 1000) + this.mergeOrAddPoll(poll) + }) }, trackPoll(pollId) { if (!this.trackedPolls[pollId]) { @@ -55,14 +50,12 @@ export const usePollsStore = defineStore('polls', { } }, votePoll({ pollId, choices }) { - return vote({ - pollId, - choices, - credentials: useOAuthStore().token, - }).then((poll) => { - this.mergeOrAddPoll(poll) - return poll - }) + return window.vuex.state.api.backendInteractor + .vote({ pollId, choices }) + .then((poll) => { + this.mergeOrAddPoll(poll) + return poll + }) }, }, }) diff --git a/src/stores/reports.js b/src/stores/reports.js index 2fea2e8e6..d3acebcb4 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -2,9 +2,6 @@ import { filter } from 'lodash' import { defineStore } from 'pinia' import { useInterfaceStore } from 'src/stores/interface.js' -import { useOAuthStore } from 'src/stores/oauth.js' - -import { setReportState } from 'src/services/api/admin.js' export const useReportsStore = defineStore('reports', { state: () => ({ @@ -41,21 +38,18 @@ export const useReportsStore = defineStore('reports', { setReportState({ id, state }) { const oldState = this.reports[id].state this.reports[id].state = state - - setReportState({ - id, - state, - credentials: useOAuthStore().token, - }).catch((e) => { - console.error('Failed to set report state', e) - useInterfaceStore().pushGlobalNotice({ - level: 'error', - messageKey: 'general.generic_error_message', - messageArgs: [e.message], - timeout: 5000, + window.vuex.state.api.backendInteractor + .setReportState({ id, state }) + .catch((e) => { + console.error('Failed to set report state', e) + useInterfaceStore().pushGlobalNotice({ + level: 'error', + messageKey: 'general.generic_error_message', + messageArgs: [e.message], + timeout: 5000, + }) + this.reports[id].state = oldState }) - this.reports[id].state = oldState - }) }, addReport(report) { this.reports[report.id] = report diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index 348579b72..3010fc738 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -22,7 +22,6 @@ import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/updat import { useInstanceStore } from 'src/stores/instance.js' import { useLocalConfigStore } from 'src/stores/local_config.js' -import { useOAuthStore } from 'src/stores/oauth.js' import { storage } from 'src/lib/storage.js' import { @@ -32,7 +31,6 @@ import { validateSetting, } from 'src/modules/default_config_state.js' import { oldDefaultConfigSync } from 'src/modules/old_default_config_state.js' -import { updateProfileJSON } from 'src/services/api/api.service.js' export const VERSION = 2 export const NEW_USER_DATE = new Date('2026-03-16') // date of writing this, basically @@ -791,10 +789,7 @@ export const useSyncConfigStore = defineStore('sync_config', { if (!needPush) return this.updateCache({ username: window.vuex.state.users.currentUser.fqn }) const params = { pleroma_settings_store: { 'pleroma-fe': this.cache } } - updateProfileJSON({ - params, - credentials: useOAuthStore().token, - }) + window.vuex.state.api.backendInteractor.updateProfileJSON({ params }) }, }, persist: { diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index b2cd680f5..f41c41628 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -14,10 +14,7 @@ import { import { defineStore } from 'pinia' import { toRaw } from 'vue' -import { useOAuthStore } from 'src/stores/oauth.js' - import { storage } from 'src/lib/storage.js' -import { updateProfileJSON } from 'src/services/api/api.service.js' export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically @@ -347,13 +344,12 @@ export const useUserHighlightStore = defineStore('user_highlight', { const params = { pleroma_settings_store: { user_highlight: this.cache }, } - updateProfileJSON({ - params, - credentials: useOAuthStore().token, - }).then((user) => { - this.initUserHighlight(user) - this.dirty = false - }) + window.vuex.state.api.backendInteractor + .updateProfileJSON({ params }) + .then((user) => { + this.initUserHighlight(user) + this.dirty = false + }) }, }, persist: { diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js index 36d323575..8b198420b 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -4,6 +4,7 @@ import { createStore } from 'vuex' import UserProfile from 'src/components/user_profile/user_profile.vue' import { getters } from 'src/modules/users.js' +import backendInteractorService from 'src/services/backend_interactor_service/backend_interactor_service.js' const mutations = { clearTimeline: () => { @@ -52,6 +53,10 @@ const externalProfileStore = createStore({ actions, getters: testGetters, state: { + api: { + fetchers: {}, + backendInteractor: backendInteractorService(''), + }, interface: { browserSupport: '', }, @@ -111,6 +116,10 @@ const localProfileStore = createStore({ actions, getters: testGetters, state: { + api: { + fetchers: {}, + backendInteractor: backendInteractorService(''), + }, interface: { browserSupport: '', },