diff --git a/src/boot/after_store.js b/src/boot/after_store.js index fe65a7387..895fe910c 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -19,7 +19,6 @@ 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' @@ -38,7 +37,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' +import { useOAuthStore } from 'src/stores/oauth.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' @@ -261,16 +260,6 @@ 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({ @@ -461,14 +450,13 @@ 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.getUserToken) { - return store.dispatch('loginUser', oauth.getUserToken) + if (oauth.userToken) { + return store.dispatch('loginUser', oauth.userToken) } return Promise.resolve() } diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js index 3c73198c5..385a6f796 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.includes( + this.currentUser.privileges.has( '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 deleted file mode 100644 index 37b3f2e5e..000000000 --- a/src/components/bookmark_folder_card/bookmark_folder_card.js +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 9e8bef618..000000000 --- a/src/components/bookmark_folder_card/bookmark_folder_card.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - - diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js index 43aa239b2..bc30790f6 100644 --- a/src/components/bookmark_folder_edit/bookmark_folder_edit.js +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -1,8 +1,10 @@ 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() { @@ -22,8 +24,10 @@ const BookmarkFolderEdit = { }, created() { if (!this.id) return - const credentials = this.$store.state.users.currentUser.credentials - apiService.fetchBookmarkFolders({ credentials }).then((folders) => { + + fetchBookmarkFolders({ + credentials: useOAuthStore().token, + }).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 9fd62dae0..cd12d4c3a 100644 --- a/src/components/bookmark_folders/bookmark_folders.js +++ b/src/components/bookmark_folders/bookmark_folders.js @@ -1,4 +1,4 @@ -import BookmarkFolderCard from 'src/components/bookmark_folder_card/bookmark_folder_card.vue' +import FolderCard from 'src/components/folder_card/folder_card.vue' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' @@ -9,7 +9,7 @@ const BookmarkFolders = { } }, components: { - BookmarkFolderCard, + FolderCard, }, computed: { bookmarkFolders() { diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue index fdd461064..56c9b62ce 100644 --- a/src/components/bookmark_folders/bookmark_folders.vue +++ b/src/components/bookmark_folders/bookmark_folders.vue @@ -12,14 +12,28 @@
- - + + + + {{ $t('nav.all_bookmarks') }} + +
+ diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 7428c0dc7..bd71c2b5d 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -17,6 +17,13 @@ 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' @@ -115,7 +122,6 @@ const Chat = { mobileLayout: (store) => store.layoutType === 'mobile', }), ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, currentUser: (state) => state.users.currentUser, }), @@ -267,42 +273,46 @@ const Chat = { const fetchOlderMessages = !!maxId const sinceId = fetchLatest && chatMessageService.maxId - 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) - } + 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) + } - 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 this.backendInteractor.getOrCreateChat({ + chat = await getOrCreateChat({ accountId: this.recipientId, + credentials: useOAuthStore().token, }) } catch (e) { console.error('Error creating or getting a chat', e) @@ -369,8 +379,10 @@ const Chat = { doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { if (retriesLeft <= 0) return - this.backendInteractor - .sendChatMessage(params) + sendChatMessage({ + params, + credentials: useOAuthStore().token, + }) .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 dcef6380e..d570c3e1b 100644 --- a/src/components/chat_list_item/chat_list_item.scss +++ b/src/components/chat_list_item/chat_list_item.scss @@ -4,6 +4,7 @@ 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 5ee2b610d..7b08d3163 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -3,6 +3,10 @@ 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' @@ -22,7 +26,9 @@ const chatNew = { } }, async created() { - const { chats } = await this.backendInteractor.chats() + const { chats } = await chats({ + credentials: useOAuthStore().token, + }) chats.forEach((chat) => this.suggestions.push(chat.account)) }, computed: { @@ -38,7 +44,6 @@ 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 da15c79d6..6bc0b7ad0 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -9,6 +9,9 @@ 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 { @@ -436,22 +439,26 @@ const conversation = { methods: { fetchConversation() { if (this.status) { - 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) - }) + 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) + }) } else { this.loadStatusError = null - this.$store.state.api.backendInteractor - .fetchStatus({ id: this.statusId }) + fetchStatus({ + id: this.statusId, + credentials: useOAuthStore().token, + }) .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 new file mode 100644 index 000000000..7a68bc3ac --- /dev/null +++ b/src/components/folder_card/folder_card.js @@ -0,0 +1,38 @@ +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 new file mode 100644 index 000000000..043a326cc --- /dev/null +++ b/src/components/folder_card/folder_card.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 44658e985..178f36894 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -4,6 +4,9 @@ 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'], @@ -48,7 +51,10 @@ const FollowRequestCard = { } }, doApprove() { - this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + approveUser({ + id: this.user.id, + credentials: useOAuthStore().token, + }) this.$store.dispatch('removeFollowRequest', this.user) const notifId = this.findFollowRequestNotificationId() @@ -70,12 +76,14 @@ const FollowRequestCard = { }, doDeny() { const notifId = this.findFollowRequestNotificationId() - this.$store.state.api.backendInteractor - .denyUser({ id: this.user.id }) - .then(() => { - this.$store.dispatch('dismissNotificationLocal', { id: notifId }) - this.$store.dispatch('removeFollowRequest', this.user) - }) + + denyUser({ + id: this.user.id, + credentials: useOAuthStore().token, + }).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 c478e9bc4..365c1ee92 100644 --- a/src/components/list/list.css +++ b/src/components/list/list.css @@ -33,6 +33,10 @@ &: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 9dcb7636c..7545d9126 100644 --- a/src/components/lists/lists.js +++ b/src/components/lists/lists.js @@ -1,4 +1,4 @@ -import ListsCard from 'src/components/lists_card/lists_card.vue' +import FolderCard from 'src/components/folder_card/folder_card.vue' import { useListsStore } from 'src/stores/lists.js' @@ -9,7 +9,7 @@ const Lists = { } }, components: { - ListsCard, + FolderCard, }, computed: { lists() { diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue index 05df5b72f..f3987205e 100644 --- a/src/components/lists/lists.vue +++ b/src/components/lists/lists.vue @@ -14,10 +14,12 @@
-
diff --git a/src/components/lists_card/lists_card.js b/src/components/lists_card/lists_card.js deleted file mode 100644 index 81b811534..000000000 --- a/src/components/lists_card/lists_card.js +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index a5dc6371e..000000000 --- a/src/components/lists_card/lists_card.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 856850920..8fc77d91d 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -15,8 +15,10 @@ 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' @@ -142,7 +144,10 @@ const Notification = { } }, doApprove() { - this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + approveUser({ + id: this.user.id, + credentials: useOAuthStore().token, + }) this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id, @@ -163,14 +168,15 @@ const Notification = { } }, doDeny() { - 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) + denyUser({ + id: this.user.id, + credentials: useOAuthStore().token, + }).then(() => { + this.$store.dispatch('dismissNotificationLocal', { + id: this.notification.id, }) + 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 430f56c84..bdef5b2a4 100644 --- a/src/components/remote_user_resolver/remote_user_resolver.js +++ b/src/components/remote_user_resolver/remote_user_resolver.js @@ -1,3 +1,7 @@ +import { useOAuthStore } from 'src/stores/oauth.js' + +import { fetchUser } from 'src/services/api/api.service.js' + const RemoteUserResolver = { data: () => ({ error: false, @@ -7,10 +11,11 @@ const RemoteUserResolver = { }, methods: { redirect() { - const acct = - this.$route.params.username + '@' + this.$route.params.hostname - this.$store.state.api.backendInteractor - .fetchUser({ id: acct }) + const id = this.$route.params.username + '@' + this.$route.params.hostname + fetchUser({ + id, + credentials: useOAuthStore().token, + }) .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 98c0cb467..f64128c57 100644 --- a/src/components/settings_modal/admin_tabs/emoji_tab.js +++ b/src/components/settings_modal/admin_tabs/emoji_tab.js @@ -11,6 +11,7 @@ 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' @@ -98,10 +99,10 @@ const EmojiTab = { methods: { reloadEmoji() { - this.$store.state.api.backendInteractor.reloadEmoji() + useAdminSettingsStore().reloadEmoji() }, importFromFS() { - this.$store.state.api.backendInteractor.importEmojiFromFS() + useAdminSettingsStore().importEmojiFromFS() }, emojiAddr(name) { if (this.pack.remote !== undefined) { @@ -113,7 +114,7 @@ const EmojiTab = { }, createEmojiPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .createEmojiPack({ name: this.newPackName }) .then((resp) => resp.json()) .then((resp) => { @@ -130,7 +131,7 @@ const EmojiTab = { }) }, deleteEmojiPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .deleteEmojiPack({ name: this.packName }) .then((resp) => resp.json()) .then((resp) => { @@ -157,7 +158,7 @@ const EmojiTab = { return edited !== def }, savePackMetadata() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }) .then((resp) => resp.json()) .then((resp) => { @@ -182,7 +183,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - this.$store.state.api.backendInteractor.listEmojiPacks, + useAdminSettingsStore().listEmojiPacks, ) .then((allPacks) => { this.knownLocalPacks = allPacks @@ -195,7 +196,7 @@ const EmojiTab = { useEmojiStore() .getAdminPacks( this.remotePackInstance, - this.$store.state.api.backendInteractor.listRemoteEmojiPacks, + useAdminSettingsStore().listRemoteEmojiPacks, ) .then((allPacks) => { let inst = this.remotePackInstance @@ -226,7 +227,7 @@ const EmojiTab = { this.remotePackDownloadAs = this.pack.remote.baseName } - this.$store.state.api.backendInteractor + useAdminSettingsStore() .downloadRemoteEmojiPack({ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, @@ -247,7 +248,7 @@ const EmojiTab = { }) }, downloadRemoteURLPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .downloadRemoteEmojiPackZIP({ url: this.remotePackURL, packName: this.newPackName, @@ -268,7 +269,7 @@ const EmojiTab = { }) }, downloadRemoteFilePack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .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 9bec3d763..b2e6c10d0 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 - this.$store.state.api.backendInteractor + useAdminSettingsStore() .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 e26a1b921..8b7b1f4e3 100644 --- a/src/components/settings_modal/admin_tabs/frontends_tab.scss +++ b/src/components/settings_modal/admin_tabs/frontends_tab.scss @@ -1,5 +1,6 @@ .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 4f6270b8d..a40d6f995 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 7078d2488..6cee8cebe 100644 --- a/src/components/settings_modal/helpers/emoji_editing_popover.vue +++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue @@ -153,6 +153,14 @@ 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, @@ -243,14 +251,14 @@ export default { saveEditedEmoji() { if (!this.isEdited) return - this.$store.state.api.backendInteractor - .updateEmojiFile({ - packName: this.packName, - shortcode: this.shortcode, - newShortcode: this.editedShortcode, - newFilename: this.editedFile, - force: false, - }) + updateEmojiFile({ + packName: this.packName, + shortcode: this.shortcode, + newShortcode: this.editedShortcode, + newFilename: this.editedFile, + force: false, + credentials: useOAuthStore().token, + }) .then((resp) => { if (resp.error !== undefined) { this.$emit('displayError', resp.error) @@ -263,18 +271,18 @@ export default { }, uploadEmoji() { let packName = this.remote === undefined ? this.packName : this.copyToPack - 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, - }) + 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, + }) .then((resp) => resp.json()) .then((resp) => { if (resp.error !== undefined) { @@ -297,8 +305,11 @@ export default { deleteEmoji() { this.deleteModalVisible = false - this.$store.state.api.backendInteractor - .deleteEmojiFile({ packName: this.packName, shortcode: this.shortcode }) + deleteEmojiFile({ + packName: this.packName, + shortcode: this.shortcode, + credentials: useOAuthStore().token, + }) .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 68d2b923a..77686b736 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -12,7 +12,9 @@ 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, @@ -484,8 +486,10 @@ const AppearanceTab = { } this.backgroundUploading = true - this.$store.state.api.backendInteractor - .updateProfileImages({ background }) + updateProfileImages({ + background, + credentials: useOAuthStore().token, + }) .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 ab8d0101d..90d80cbf6 100644 --- a/src/components/settings_modal/tabs/composing_tab.js +++ b/src/components/settings_modal/tabs/composing_tab.js @@ -14,8 +14,10 @@ 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' @@ -164,12 +166,13 @@ const ComposingTab = { ), } - this.$store.state.api.backendInteractor - .updateProfile({ params }) - .then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + updateProfile({ + params, + credentials: useOAuthStore().token, + }).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 cc41302c7..05af7d27c 100644 --- a/src/components/settings_modal/tabs/data_import_export_tab.js +++ b/src/components/settings_modal/tabs/data_import_export_tab.js @@ -4,8 +4,20 @@ 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 { @@ -28,42 +40,51 @@ const DataImportExportTab = { }, computed: { ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, user: (state) => state.users.currentUser, }), }, methods: { getFollowsContent() { - return this.backendInteractor - .exportFriends({ id: this.user.id }) - .then(this.generateExportableUsersContent) + return exportFriends({ + id: this.user.id, + credentials: useOAuthStore().token, + }).then(this.generateExportableUsersContent) }, getBlocksContent() { - return this.backendInteractor - .fetchBlocks() - .then(this.generateExportableUsersContent) + return fetchBlocks({ + credentials: useOAuthStore().token, + }).then(this.generateExportableUsersContent) }, getMutesContent() { - return this.backendInteractor - .fetchMutes() - .then(this.generateExportableUsersContent) + return fetchMutes({ + credentials: useOAuthStore().token, + }).then(this.generateExportableUsersContent) }, importFollows(file) { - return this.backendInteractor.importFollows({ file }).then((status) => { + return importFollows({ + file, + credentials: useOAuthStore().token, + }).then((status) => { if (!status) { throw new Error('failed') } }) }, importBlocks(file) { - return this.backendInteractor.importBlocks({ file }).then((status) => { + return importBlocks({ + file, + credentials: useOAuthStore().token, + }).then((status) => { if (!status) { throw new Error('failed') } }) }, importMutes(file) { - return this.backendInteractor.importMutes({ file }).then((status) => { + return importMutes({ + file, + credentials: useOAuthStore().token, + }).then((status) => { if (!status) { throw new Error('failed') } @@ -83,8 +104,9 @@ const DataImportExportTab = { .join('\n') }, addBackup() { - this.$store.state.api.backendInteractor - .addBackup() + addBackup({ + credentials: useOAuthStore().token, + }) .then(() => { this.addedBackup = true this.addBackupError = false @@ -96,8 +118,9 @@ const DataImportExportTab = { .then(() => this.fetchBackups()) }, fetchBackups() { - this.$store.state.api.backendInteractor - .listBackups() + listBackups({ + credentials: useOAuthStore().token, + }) .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 db1177cb2..893833866 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -12,8 +12,10 @@ 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 = { @@ -58,12 +60,13 @@ const GeneralTab = { ), } - this.$store.state.api.backendInteractor - .updateProfile({ params }) - .then((user) => { - this.$store.commit('addNewUsers', [user]) - this.$store.commit('setCurrentUser', user) - }) + updateProfile({ + params, + credentials: useOAuthStore().token, + }).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 0d889ce54..d1f5bff12 100644 --- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js +++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js @@ -10,8 +10,11 @@ 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 { @@ -54,22 +57,24 @@ const MutesAndBlocks = { return () => this.$store.dispatch('fetch' + group, this.userId) }, importFollows(file) { - return this.$store.state.api.backendInteractor - .importFollows({ file }) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return importFollows({ + file, + credentials: useOAuthStore().token, + }).then((status) => { + if (!status) { + throw new Error('failed') + } + }) }, importBlocks(file) { - return this.$store.state.api.backendInteractor - .importBlocks({ file }) - .then((status) => { - if (!status) { - throw new Error('failed') - } - }) + return importBlocks({ + file, + credentials: useOAuthStore().token, + }).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 5114012a8..6ef8e0ab7 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,6 +1,10 @@ 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 { @@ -27,7 +31,8 @@ const NotificationsTab = { }, methods: { updateNotificationSettings() { - this.$store.state.api.backendInteractor.updateNotificationSettings({ + updateNotificationSettings({ + credentials: useOAuthStore().token, settings: this.notificationSettings, }) }, diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 3e12f265f..586ca30a8 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -3,6 +3,10 @@ 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, @@ -35,9 +39,10 @@ const ProfileTab = { const params = { locked: this.locked, } - - this.$store.state.api.backendInteractor - .updateProfile({ params }) + updateProfile({ + params, + credentials: useOAuthStore().token, + }) .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 a998fdb4b..bba2a2ff9 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa.js +++ b/src/components/settings_modal/tabs/security_tab/mfa.js @@ -5,6 +5,15 @@ 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: { @@ -71,9 +80,6 @@ const Mfa = { confirmNewBackupCodes() { return this.backupCodes.getNewCodes }, - ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, - }), }, methods: { @@ -87,7 +93,9 @@ const Mfa = { this.backupCodes.inProgress = true this.backupCodes.codes = [] - return this.backendInteractor.generateMfaBackupCodes().then((res) => { + return generateMfaBackupCodes({ + credentials: useOAuthStore().token, + }).then((res) => { this.backupCodes.codes = res.codes this.backupCodes.inProgress = false }) @@ -112,7 +120,9 @@ const Mfa = { // prepare setup OTP this.setupState.state = 'setupOTP' this.setupState.setupOTPState = 'prepare' - this.backendInteractor.mfaSetupOTP().then((res) => { + mfaSetupOTP({ + credentials: useOAuthStore().token, + }).then((res) => { this.otpSettings = res this.setupState.setupOTPState = 'confirm' }) @@ -120,18 +130,17 @@ const Mfa = { doConfirmOTP() { // handler confirm enable OTP this.error = null - this.backendInteractor - .mfaConfirmOTP({ - token: this.otpConfirmToken, - password: this.currentPassword, - }) - .then((res) => { - if (res.error) { - this.error = res.error - return - } - this.completeSetup() - }) + mfaConfirmOTP({ + token: this.otpConfirmToken, + password: this.currentPassword, + credentials: useOAuthStore().token, + }).then((res) => { + if (res.error) { + this.error = res.error + return + } + this.completeSetup() + }) }, completeSetup() { @@ -152,7 +161,9 @@ const Mfa = { // fetch settings from server async fetchSettings() { - const result = await this.backendInteractor.settingsMFA() + const result = await settingsMFA({ + credentials: useOAuthStore().token, + }) 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 e011fd638..b7a1426ab 100644 --- a/src/components/settings_modal/tabs/security_tab/mfa_totp.js +++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.js @@ -2,6 +2,10 @@ 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: () => ({ @@ -17,9 +21,6 @@ export default { isActivated() { return this.settings.totp }, - ...mapState({ - backendInteractor: (state) => state.api.backendInteractor, - }), }, methods: { doActivate() { @@ -36,19 +37,18 @@ export default { // confirm deactivate TOTP method this.error = null this.inProgress = true - 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') - }) + 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') + }) }, }, } 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 96510edcf..ca959ded5 100644 --- a/src/components/settings_modal/tabs/security_tab/security_tab.js +++ b/src/components/settings_modal/tabs/security_tab/security_tab.js @@ -4,8 +4,18 @@ 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 = { @@ -65,78 +75,79 @@ const SecurityTab = { this.deletingAccount = true }, deleteAccount() { - 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 - } - }) + 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 + } + }) }, changePassword() { const params = { password: this.changePasswordInputs[0], newPassword: this.changePasswordInputs[1], newPasswordConfirmation: this.changePasswordInputs[2], + credentials: useOAuthStore().token, } - 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 - } - }) + 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, } - 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 - } - }) + 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, } - 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 - } - }) + moveAccount(params).then((res) => { + if (res.status === 'success') { + this.movedAccount = true + this.moveAccountError = false + } else { + this.movedAccount = false + this.moveAccountError = res.error + } + }) }, removeAlias(alias) { - this.$store.state.api.backendInteractor - .deleteAlias({ alias }) - .then(() => this.fetchAliases()) + deleteAlias({ + alias, + credentials: useOAuthStore().token, + }).then(() => this.fetchAliases()) }, addAlias() { - this.$store.state.api.backendInteractor - .addAlias({ alias: this.addAliasTarget }) + addAlias({ + alias: this.addAliasTarget, + credentials: useOAuthStore().token, + }) .then(() => { this.addedAlias = true this.addAliasError = false @@ -149,8 +160,9 @@ const SecurityTab = { .then(() => this.fetchAliases()) }, fetchAliases() { - this.$store.state.api.backendInteractor - .listAliases() + listAliases({ + credentials: useOAuthStore().token, + }) .then((res) => { this.aliases = res.aliases this.listAliasesError = false diff --git a/src/components/status/status.js b/src/components/status/status.js index 0107bde3c..01d7facfc 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -131,40 +131,41 @@ const Status = { Quote: defineAsyncComponent(() => import('src/components/quote/quote.vue')), StatusActionButtons, }, - props: [ - 'statusoid', - 'replies', + props: { + statusoid: Object, + replies: Array, - 'expandable', - 'focused', - 'highlight', - 'compact', - 'isPreview', - 'noHeading', - 'inlineExpanded', - 'showPinned', - 'inProfile', - 'inConversation', - 'inQuote', - 'profileUserId', - 'simpleTree', - 'showOtherRepliesAsButton', - 'dive', - 'ignoreMute', + expandable: Boolean, + focused: Boolean, + highlight: Boolean, + compact: Boolean, + isPreview: Boolean, + noHeading: Boolean, + inlineExpanded: Boolean, + showPinned: Boolean, + inProfile: Boolean, + inConversation: Boolean, + inQuote: Boolean, - 'controlledThreadDisplayStatus', - 'controlledToggleThreadDisplay', - 'controlledShowingTall', - 'controlledToggleShowingTall', - 'controlledExpandingSubject', - 'controlledToggleExpandingSubject', - 'controlledShowingLongSubject', - 'controlledToggleShowingLongSubject', - 'controlledReplying', - 'controlledToggleReplying', - 'controlledMediaPlaying', - 'controlledSetMediaPlaying', - ], + 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, + }, 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 2d0de25ef..e02b71b0c 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.error.error)) + .catch((err) => this.$emit('onError', err)) }, 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 02f036a52..92cc20904 100644 --- a/src/components/still-image/still-image-emoji-popover.js +++ b/src/components/still-image/still-image-emoji-popover.js @@ -2,6 +2,7 @@ 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' @@ -37,7 +38,7 @@ export default { }) }, copyToLocalPack() { - this.$store.state.api.backendInteractor + useAdminSettingsStore() .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 30e0bd903..755ff9a78 100644 --- a/src/components/tab_switcher/tab_switcher.scss +++ b/src/components/tab_switcher/tab_switcher.scss @@ -56,6 +56,7 @@ .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 acb2c4ea1..1a35c019b 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -22,9 +22,11 @@ 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' @@ -307,9 +309,9 @@ export default { const privileges = this.loggedIn.privileges return ( this.loggedIn.role === 'admin' || - privileges.includes('users_manage_activation_state') || - privileges.includes('users_delete') || - privileges.includes('users_manage_tags') + privileges.has('users_manage_activation_state') || + privileges.has('users_delete') || + privileges.has('users_manage_tags') ) }, hasNote() { @@ -597,8 +599,7 @@ export default { params.header = this.newBannerFile } - this.$store.state.api.backendInteractor - .updateProfile({ params }) + 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 7e57aae5a..e5118f474 100644 --- a/src/components/user_profile/user_profile_admin_view.vue +++ b/src/components/user_profile/user_profile_admin_view.vue @@ -33,6 +33,7 @@ :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 12d548054..49e569f23 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -5,8 +5,11 @@ 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, @@ -28,6 +31,7 @@ const UserReportingModal = { return !!this.$store.state.users.currentUser }, isOpen() { + console.log(this.reportModal) return this.isLoggedIn && this.reportModal.activated }, userId() { @@ -70,9 +74,9 @@ const UserReportingModal = { comment: this.comment, forward: this.forward, statusIds: [...this.statusIdsToReport], + credentials: useOAuthStore().token, } - this.$store.state.api.backendInteractor - .reportUser({ ...params }) + 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 8ca2d8c1a..be4644424 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -2,6 +2,9 @@ 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: { @@ -17,21 +20,22 @@ const WhoToFollow = { }, methods: { showWhoToFollow(reply) { - 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) - } - }) + reply.forEach(({ id }) => { + fetchUser({ + id, + credentials: useOAuthStore().token, + }).then((externalUser) => { + if (!externalUser.error) { + this.$store.commit('addNewUsers', [externalUser]) + this.users.push(externalUser) + } + }) }) }, getWhoToFollow() { - const credentials = this.$store.state.users.currentUser.credentials + const credentials = useOAuthStore().token if (credentials) { - apiService.suggestions({ credentials }).then((reply) => { + 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 cd0524ecf..fa1c12278 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,14 +18,15 @@ function showWhoToFollow(panel, reply) { toFollow.img = img toFollow.name = name - panel.$store.state.api.backendInteractor - .fetchUser({ id: name }) - .then((externalUser) => { - if (!externalUser.error) { - panel.$store.commit('addNewUsers', [externalUser]) - toFollow.id = externalUser.id - } - }) + fetchUser({ + id: name, + credentials: useOAuthStore().token, + }).then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + toFollow.id = externalUser.id + } + }) }) } @@ -35,7 +36,7 @@ function getWhoToFollow(panel) { panel.usersToFollow.forEach((toFollow) => { toFollow.name = 'Loading...' }) - apiService.suggestions({ credentials }).then((reply) => { + suggestions({ credentials }).then((reply) => { showWhoToFollow(panel, reply) }) } diff --git a/src/modules/api.js b/src/modules/api.js index 6f4b8b15f..c7de7a070 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -1,20 +1,28 @@ 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, @@ -25,9 +33,6 @@ const api = { followRequestCount: (state) => state.followRequests.length, }, mutations: { - setBackendInteractor(state, backendInteractor) { - state.backendInteractor = backendInteractor - }, addFetcher(state, { fetcherName, fetcher }) { state.fetchers[fetcherName] = fetcher }, @@ -91,9 +96,17 @@ const api = { try { const { state, commit, dispatch, rootState } = store const timelineData = rootState.statuses.timelines.friends - state.mastoUserSocket = state.backendInteractor.startUserSocket({ - store, + + 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.addEventListener( 'pleroma:authenticated', () => { @@ -245,7 +258,7 @@ const api = { return if (store.state.fetchers[timeline]) return - const fetcher = store.state.backendInteractor.startFetchingTimeline({ + const fetcher = timelineFetcher.startFetching({ timeline, store, userId, @@ -253,7 +266,9 @@ const api = { statusId, bookmarkFolderId, tag, + credentials: useOAuthStore().token, }) + store.commit('addFetcher', { fetcherName: timeline, fetcher }) }, stopFetchingTimeline(store, timeline) { @@ -261,19 +276,22 @@ const api = { if (!fetcher) return store.commit('removeFetcher', { fetcherName: timeline, fetcher }) }, + fetchTimeline(store, { timeline, ...rest }) { - store.state.backendInteractor.fetchTimeline({ + fetchTimeline({ store, timeline, ...rest, + credentials: useOAuthStore().token, }) }, // Notifications startFetchingNotifications(store) { if (store.state.fetchers.notifications) return - const fetcher = store.state.backendInteractor.startFetchingNotifications({ + const fetcher = notificationsFetcher.startFetching({ store, + credentials: useOAuthStore().token, }) store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) }, @@ -282,19 +300,14 @@ 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 = store.state.backendInteractor.startFetchingFollowRequests( - { store }, - ) + const fetcher = followRequestFetcher.startFetchingFollowRequests({ + store, + credentials: useOAuthStore().token, + }) store.commit('addFetcher', { fetcherName: 'followRequests', fetcher }) }, @@ -303,39 +316,6 @@ 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 308b2cb27..4ad82eccd 100644 --- a/src/modules/chats.js +++ b/src/modules/chats.js @@ -9,6 +9,14 @@ 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: {}, @@ -36,7 +44,7 @@ const unreadChatCount = (state) => { return sumBy(state.chatList.data, 'unread') } -const chats = { +const chatsModule = { state: { ...defaultState }, getters: { currentChat: (state) => state.openedChats[state.currentChatId], @@ -51,7 +59,6 @@ const chats = { // Chat list startFetchingChats({ dispatch, commit }) { const fetcher = () => dispatch('fetchChats', { latest: true }) - fetcher() commit('setChatListFetcher', { fetcher: () => promiseInterval(fetcher, 5000), }) @@ -60,8 +67,10 @@ const chats = { commit('setChatListFetcher', { fetcher: undefined }) }, fetchChats({ dispatch, rootState }) { - return rootState.api.backendInteractor.chats().then(({ chats }) => { - dispatch('addNewChats', { chats }) + return chats({ + credentials: useOAuthStore().token, + }).then(({ chatList }) => { + dispatch('addNewChats', { chats: chatList }) return chats }) }, @@ -113,11 +122,18 @@ const chats = { commit('readChat', { id, lastReadId }) if (isNewMessage) { - rootState.api.backendInteractor.readChat({ id, lastReadId }) + readChat({ + id, + lastReadId, + credentials: useOAuthStore().token, + }) } }, deleteChatMessage({ rootState, commit }, value) { - rootState.api.backendInteractor.deleteChatMessage(value) + deleteChatMessage({ + ...value, + credentials: useOAuthStore().token, + }) commit('deleteChatMessage', { commit, ...value }) }, resetChats({ commit, dispatch }) { @@ -262,4 +278,4 @@ const chats = { }, } -export default chats +export default chatsModule diff --git a/src/modules/notifications.js b/src/modules/notifications.js index d501b39db..3d5f787cf 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -1,4 +1,4 @@ -import apiService from '../services/api/api.service.js' +import { markNotificationsAsSeen } from '../services/api/api.service.js' import { closeAllDesktopNotifications, closeDesktopNotification, @@ -11,9 +11,12 @@ 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, @@ -154,33 +157,32 @@ export const notifications = { }, markNotificationsAsSeen({ rootState, state, commit }) { commit('markNotificationsAsSeen') - apiService - .markNotificationsAsSeen({ - id: state.maxId, - credentials: rootState.users.currentUser.credentials, - }) - .then(() => { - closeAllDesktopNotifications(rootState) - }) + markNotificationsAsSeen({ + id: state.maxId, + credentials: rootState.users.currentUser.credentials, + }).then(() => { + closeAllDesktopNotifications(rootState) + }) }, markSingleNotificationAsSeen({ rootState, commit }, { id }) { commit('markSingleNotificationAsSeen', { id }) - apiService - .markNotificationsAsSeen({ - single: true, - id, - credentials: rootState.users.currentUser.credentials, - }) - .then(() => { - closeDesktopNotification(rootState, { id }) - }) + 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 }) - rootState.api.backendInteractor.dismissNotification({ id }) + dismissNotification({ + id, + credentials: useOAuthStore().token, + }) }, updateNotification({ commit }, { id, updater }) { commit('updateNotification', { id, updater }) diff --git a/src/modules/profileConfig.js b/src/modules/profileConfig.js index 90571e21d..0f125e976 100644 --- a/src/modules/profileConfig.js +++ b/src/modules/profileConfig.js @@ -1,28 +1,37 @@ 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 rootState.api.backendInteractor - .updateProfile({ params }) - .then((result) => { - commit('addNewUsers', [result]) - commit('setCurrentUser', result) - }) + return updateProfile({ + params, + credentials: useOAuthStore().token, + }).then((result) => { + commit('addNewUsers', [result]) + commit('setCurrentUser', result) + }) } const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => { const settings = {} set(settings, path, value) - return rootState.api.backendInteractor - .updateNotificationSettings({ settings }) - .then((result) => { - if (result.status === 'success') { - commit('confirmProfileOption', { name, value }) - } else { - commit('confirmProfileOption', { name, value: oldValue }) - } - }) + return updateNotificationSettings({ + settings, + credentials: useOAuthStore().token, + }).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 b4f0a93bf..ef6aedcbc 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -13,10 +13,34 @@ import { slice, } from 'lodash' -import apiService from '../services/api/api.service.js' +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 { 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: [], @@ -131,8 +155,7 @@ const getLatestScrobble = (state, user) => { state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 if (!scrobblesSupport) return - apiService - .fetchScrobbles({ accountId: user.id }) + fetchScrobbles({ accountId: user.id }) .then((scrobbles) => { if (scrobbles?.error) { useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false) @@ -602,25 +625,24 @@ const statuses = { }) }, fetchStatus({ rootState, dispatch }, id) { - return rootState.api.backendInteractor - .fetchStatus({ id }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return fetchStatus({ id }).then((status) => + dispatch('addNewStatuses', { statuses: [status] }), + ) }, fetchStatusSource({ rootState }, status) { - return apiService.fetchStatusSource({ + return fetchStatusSource({ id: status.id, - credentials: rootState.users.currentUser.credentials, + credentials: useOAuthStore().token, }) }, fetchStatusHistory(_, status) { - return apiService.fetchStatusHistory({ status }) + return fetchStatusHistory({ status }) }, deleteStatus({ rootState, commit }, status) { - apiService - .deleteStatus({ - id: status.id, - credentials: rootState.users.currentUser.credentials, - }) + deleteStatus({ + id: status.id, + credentials: useOAuthStore().token, + }) .then(() => { commit('setDeleted', { status }) }) @@ -643,99 +665,111 @@ const statuses = { favorite({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) - rootState.api.backendInteractor - .favorite({ id: status.id }) - .then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + favorite({ + id: status.id, + credentials: useOAuthStore().token, + }).then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, unfavorite({ rootState, commit }, status) { // Optimistic unfavoriting... commit('setFavorited', { status, value: false }) - rootState.api.backendInteractor - .unfavorite({ id: status.id }) - .then((status) => - commit('setFavoritedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + unfavorite({ + id: status.id, + credentials: useOAuthStore().token, + }).then((status) => + commit('setFavoritedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, fetchPinnedStatuses({ rootState, dispatch }, userId) { - rootState.api.backendInteractor - .fetchPinnedStatuses({ id: userId }) - .then((statuses) => - dispatch('addNewStatuses', { - statuses, - timeline: 'user', - userId, - showImmediately: true, - noIdUpdate: true, - }), - ) + fetchPinnedStatuses({ + id: userId, + credentials: useOAuthStore().token, + }).then((statuses) => + dispatch('addNewStatuses', { + statuses, + timeline: 'user', + userId, + showImmediately: true, + noIdUpdate: true, + }), + ) }, pinStatus({ rootState, dispatch }, statusId) { - return rootState.api.backendInteractor - .pinOwnStatus({ id: statusId }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return pinOwnStatus({ + id: statusId, + credentials: useOAuthStore().token, + }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, unpinStatus({ rootState, dispatch }, statusId) { - return rootState.api.backendInteractor - .unpinOwnStatus({ id: statusId }) - .then((status) => dispatch('addNewStatuses', { statuses: [status] })) + return unpinOwnStatus({ + id: statusId, + credentials: useOAuthStore().token, + }).then((status) => dispatch('addNewStatuses', { statuses: [status] })) }, muteConversation({ rootState, commit }, { id: statusId }) { - return rootState.api.backendInteractor - .muteConversation({ id: statusId }) - .then((status) => commit('setMutedStatus', status)) + return muteConversation({ + id: statusId, + credentials: useOAuthStore().token, + }).then((status) => commit('setMutedStatus', status)) }, unmuteConversation({ rootState, commit }, { id: statusId }) { - return rootState.api.backendInteractor - .unmuteConversation({ id: statusId }) - .then((status) => commit('setMutedStatus', status)) + return unmuteConversation({ + id: statusId, + credentials: useOAuthStore().token, + }).then((status) => commit('setMutedStatus', status)) }, retweet({ rootState, commit }, status) { // Optimistic retweeting... commit('setRetweeted', { status, value: true }) - rootState.api.backendInteractor - .retweet({ id: status.id }) - .then((status) => - commit('setRetweetedConfirm', { - status: status.retweeted_status, - user: rootState.users.currentUser, - }), - ) + retweet({ + id: status.id, + credentials: useOAuthStore().token, + }).then((status) => + commit('setRetweetedConfirm', { + status: status.retweeted_status, + user: rootState.users.currentUser, + }), + ) }, unretweet({ rootState, commit }, status) { // Optimistic unretweeting... commit('setRetweeted', { status, value: false }) - rootState.api.backendInteractor - .unretweet({ id: status.id }) - .then((status) => - commit('setRetweetedConfirm', { - status, - user: rootState.users.currentUser, - }), - ) + unretweet({ + id: status.id, + credentials: useOAuthStore().token, + }).then((status) => + commit('setRetweetedConfirm', { + status, + user: rootState.users.currentUser, + }), + ) }, bookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: true }) - rootState.api.backendInteractor - .bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id }) - .then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + bookmarkStatus({ + id: status.id, + folder_id: status.bookmark_folder_id, + credentials: useOAuthStore().token, + }).then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, unbookmark({ rootState, commit }, status) { commit('setBookmarked', { status, value: false }) - rootState.api.backendInteractor - .unbookmarkStatus({ id: status.id }) - .then((status) => { - commit('setBookmarkedConfirm', { status }) - }) + unbookmarkStatus({ + id: status.id, + credentials: useOAuthStore().token, + }).then((status) => { + commit('setBookmarkedConfirm', { status }) + }) }, queueFlush({ commit }, { timeline, id }) { commit('queueFlush', { timeline, id }) @@ -745,8 +779,14 @@ const statuses = { }, fetchFavsAndRepeats({ rootState, commit }, id) { Promise.all([ - rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), - rootState.api.backendInteractor.fetchRebloggedByUsers({ id }), + fetchFavoritedByUsers({ + id, + credentials: useOAuthStore().token, + }), + fetchRebloggedByUsers({ + id, + credentials: useOAuthStore().token, + }), ]).then(([favoritedByUsers, rebloggedByUsers]) => { commit('addFavs', { id, @@ -765,7 +805,11 @@ const statuses = { if (!currentUser) return commit('addOwnReaction', { id, emoji, currentUser }) - rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => { + reactWithEmoji({ + id, + emoji, + credentials: useOAuthStore().token, + }).then(() => { dispatch('fetchEmojiReactionsBy', id) }) }, @@ -774,59 +818,70 @@ const statuses = { if (!currentUser) return commit('removeOwnReaction', { id, emoji, currentUser }) - rootState.api.backendInteractor - .unreactWithEmoji({ id, emoji }) - .then(() => { - dispatch('fetchEmojiReactionsBy', id) - }) + unreactWithEmoji({ + id, + emoji, + currentUser: rootState.users.currentUser, + }).then(() => { + dispatch('fetchEmojiReactionsBy', id) + }) }, fetchEmojiReactionsBy({ rootState, commit }, id) { - return rootState.api.backendInteractor - .fetchEmojiReactions({ id }) - .then((emojiReactions) => { - commit('addEmojiReactionsBy', { - id, - emojiReactions, - currentUser: rootState.users.currentUser, - }) + return fetchEmojiReactions({ + id, + credentials: useOAuthStore().token, + }).then((emojiReactions) => { + commit('addEmojiReactionsBy', { + id, + emojiReactions, + currentUser: rootState.users.currentUser, }) + }) }, fetchFavs({ rootState, commit }, id) { - rootState.api.backendInteractor - .fetchFavoritedByUsers({ id }) - .then((favoritedByUsers) => - commit('addFavs', { - id, - favoritedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + fetchFavoritedByUsers({ + id, + credentials: useOAuthStore().token, + }).then((favoritedByUsers) => + commit('addFavs', { + id, + favoritedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, fetchRepeats({ rootState, commit }, id) { - rootState.api.backendInteractor - .fetchRebloggedByUsers({ id }) - .then((rebloggedByUsers) => - commit('addRepeats', { - id, - rebloggedByUsers, - currentUser: rootState.users.currentUser, - }), - ) + fetchRebloggedByUsers({ + id, + credentials: useOAuthStore().token, + }).then((rebloggedByUsers) => + commit('addRepeats', { + id, + rebloggedByUsers, + currentUser: rootState.users.currentUser, + }), + ) }, search(store, { q, resolve, limit, offset, following, type }) { - 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 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 data + }) }, setVirtualHeight({ commit }, { statusId, height }) { commit('setVirtualHeight', { statusId, height }) diff --git a/src/modules/users.js b/src/modules/users.js index ec70f8105..4a4614e4d 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -9,8 +9,7 @@ import { uniq, } from 'lodash' -import apiService from '../services/api/api.service.js' -import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' +import { register } from '../services/api/api.service.js' import oauthApi from '../services/new_api/oauth.js' import { registerPushNotifications, @@ -21,15 +20,34 @@ 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) { @@ -72,46 +90,38 @@ const blockUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addBlockId', id) - return store.rootState.api.backendInteractor - .blockUser({ id, expiresIn }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addBlockId', id) + return 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 store.rootState.api.backendInteractor - .unblockUser({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return unblockUser({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const removeUserFromFollowers = (store, id) => { - return store.rootState.api.backendInteractor - .removeUserFromFollowers({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return removeUserFromFollowers({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const editUserNote = (store, { id, comment }) => { - return store.rootState.api.backendInteractor - .editUserNote({ id, comment }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return editUserNote({ id, comment }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } -const muteUser = (store, args) => { +const localMuteUser = (store, args) => { const id = typeof args === 'object' ? args.id : args const expiresIn = typeof args === 'object' ? args.expiresIn : 0 @@ -119,12 +129,14 @@ const muteUser = (store, args) => { store.commit('updateUserRelationship', [predictedRelationship]) store.commit('addMuteId', id) - return store.rootState.api.backendInteractor - .muteUser({ id, expiresIn }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - store.commit('addMuteId', id) - }) + return muteUser({ + id, + expiresIn, + credentials: useOAuthStore().token, + }).then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + store.commit('addMuteId', id) + }) } const unmuteUser = (store, id) => { @@ -132,39 +144,43 @@ const unmuteUser = (store, id) => { predictedRelationship.muting = false store.commit('updateUserRelationship', [predictedRelationship]) - return store.rootState.api.backendInteractor - .unmuteUser({ id }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return unmuteUser({ id }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const hideReblogs = (store, userId) => { - return store.rootState.api.backendInteractor - .followUser({ id: userId, reblogs: false }) - .then((relationship) => { - store.commit('updateUserRelationship', [relationship]) - }) + return followUser({ + id: userId, + reblogs: false, + credentials: useOAuthStore().token, + }).then((relationship) => { + store.commit('updateUserRelationship', [relationship]) + }) } const showReblogs = (store, userId) => { - return store.rootState.api.backendInteractor - .followUser({ id: userId, reblogs: true }) - .then((relationship) => - store.commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id: userId, + reblogs: true, + credentials: useOAuthStore().token, + }).then((relationship) => + store.commit('updateUserRelationship', [relationship]), + ) } const muteDomain = (store, domain) => { - return store.rootState.api.backendInteractor - .muteDomain({ domain }) - .then(() => store.commit('addDomainMute', domain)) + return muteDomain({ + domain, + credentials: useOAuthStore().token, + }).then(() => store.commit('addDomainMute', domain)) } const unmuteDomain = (store, domain) => { - return store.rootState.api.backendInteractor - .unmuteDomain({ domain }) - .then(() => store.commit('removeDomainMute', domain)) + return unmuteDomain({ + domain, + credentials: useOAuthStore().token, + }).then(() => store.commit('removeDomainMute', domain)) } export const mutations = { @@ -385,55 +401,60 @@ const users = { }) }, fetchUser(store, id) { - return store.rootState.api.backendInteractor - .fetchUser({ id }) - .then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return fetchUser({ + id, + credentials: useOAuthStore().token, + }).then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserByName(store, name) { - return store.rootState.api.backendInteractor - .fetchUserByName({ name }) - .then((user) => { - store.commit('addNewUsers', [user]) - return user - }) + return fetchUserByName({ + name, + credentials: useOAuthStore().token, + }).then((user) => { + store.commit('addNewUsers', [user]) + return user + }) }, fetchUserRelationship(store, id) { if (store.state.currentUser) { - store.rootState.api.backendInteractor - .fetchUserRelationship({ id }) - .then((relationships) => - store.commit('updateUserRelationship', relationships), - ) + fetchUserRelationship({ + id, + credentials: useOAuthStore().token, + }).then((relationships) => + store.commit('updateUserRelationship', relationships), + ) } }, fetchUserInLists(store, id) { if (store.state.currentUser) { - store.rootState.api.backendInteractor - .fetchUserInLists({ id }) - .then((inLists) => store.commit('updateUserInLists', { id, inLists })) + fetchUserInLists({ + id, + credentials: useOAuthStore().token, + }).then((inLists) => store.commit('updateUserInLists', { id, inLists })) } }, fetchBlocks(store, args) { const { reset } = args || {} const maxId = store.state.currentUser.blockIdsMaxId - 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 - }) + 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 + }) }, blockUser(store, data) { return blockUser(store, data) @@ -457,23 +478,24 @@ const users = { const { reset } = args || {} const maxId = store.state.currentUser.muteIdsMaxId - 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 - }) + 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 + }) }, muteUser(store, data) { - return muteUser(store, data) + return localMuteUser(store, data) }, unmuteUser(store, id) { return unmuteUser(store, id) @@ -485,18 +507,18 @@ const users = { return showReblogs(store, id) }, muteUsers(store, data = []) { - return Promise.all(data.map((d) => muteUser(store, d))) + return Promise.all(data.map((d) => localMuteUser(store, d))) }, unmuteUsers(store, ids = []) { return Promise.all(ids.map((d) => unmuteUser(store, d))) }, fetchDomainMutes(store) { - return store.rootState.api.backendInteractor - .fetchDomainMutes() - .then((domainMutes) => { - store.commit('saveDomainMutes', domainMutes) - return domainMutes - }) + return fetchDomainMutes({ + credentials: useOAuthStore().token, + }).then((domainMutes) => { + store.commit('saveDomainMutes', domainMutes) + return domainMutes + }) }, muteDomain(store, domain) { return muteDomain(store, domain) @@ -513,24 +535,28 @@ const users = { fetchFriends({ rootState, commit }, id) { const user = rootState.users.usersObject[id] const maxId = last(user.friendIds) - return rootState.api.backendInteractor - .fetchFriends({ id, maxId }) - .then((friends) => { - commit('addNewUsers', friends) - commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) - return friends - }) + return fetchFriends({ + id, + maxId, + credentials: useOAuthStore().token, + }).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 rootState.api.backendInteractor - .fetchFollowers({ id, maxId }) - .then((followers) => { - commit('addNewUsers', followers) - commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) - return followers - }) + return fetchFollowers({ + id, + maxId, + credentials: useOAuthStore().token, + }).then((followers) => { + commit('addNewUsers', followers) + commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) + return followers + }) }, clearFriends({ commit }, userId) { commit('clearFriends', userId) @@ -539,18 +565,22 @@ const users = { commit('clearFollowers', userId) }, subscribeUser({ rootState, commit }, id) { - return rootState.api.backendInteractor - .followUser({ id, notify: true }) - .then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id, + notify: true, + credentials: useOAuthStore().token, + }).then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, unsubscribeUser({ rootState, commit }, id) { - return rootState.api.backendInteractor - .followUser({ id, notify: false }) - .then((relationship) => - commit('updateUserRelationship', [relationship]), - ) + return followUser({ + id, + notify: false, + credentials: useOAuthStore().token, + }).then((relationship) => + commit('updateUserRelationship', [relationship]), + ) }, registerPushNotifications(store) { const token = store.state.currentUser.credentials @@ -611,12 +641,13 @@ const users = { }) }, searchUsers({ rootState, commit }, { query }) { - return rootState.api.backendInteractor - .searchUsers({ query }) - .then((users) => { - commit('addNewUsers', users) - return users - }) + return searchUsers({ + query, + credentials: useOAuthStore().token, + }).then((users) => { + commit('addNewUsers', users) + return users + }) }, async signUp(store, userInfo) { const oauthStore = useOAuthStore() @@ -624,7 +655,7 @@ const users = { try { const token = await oauthStore.ensureAppToken() - const data = await apiService.register({ + const data = await register({ credentials: token, params: { ...userInfo }, }) @@ -645,8 +676,10 @@ const users = { throw e } }, - async getCaptcha(store) { - return store.rootState.api.backendInteractor.getCaptcha() + getCaptcha(store) { + return getCaptcha({ + credentials: useOAuthStore().token, + }) }, logout(store) { @@ -670,13 +703,9 @@ const users = { store.dispatch('disconnectFromSocket') oauth.clearToken() store.dispatch('stopFetchingTimeline', 'friends') - store.commit( - 'setBackendInteractor', - backendInteractorService(oauth.getToken), - ) store.dispatch('stopFetchingNotifications') - store.dispatch('stopFetchingLists') - store.dispatch('stopFetchingBookmarkFolders') + useListsStore().stopFetching() + useBookmarkFoldersStore().stopFetching() store.dispatch('stopFetchingFollowRequests') store.commit('clearNotifications') store.commit('resetStatuses') @@ -690,9 +719,12 @@ const users = { return new Promise((resolve, reject) => { const commit = store.commit const dispatch = store.dispatch + commit('beginLogin') - store.rootState.api.backendInteractor - .verifyCredentials(accessToken) + + verifyCredentials({ + credentials: useOAuthStore().token, + }) .then((data) => { if (!data.error) { const user = data @@ -721,12 +753,6 @@ 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 @@ -764,8 +790,8 @@ const users = { } } - dispatch('startFetchingLists') - dispatch('startFetchingBookmarkFolders') + useListsStore().startFetching() + useBookmarkFoldersStore().startFetching() if (user.locked) { dispatch('startFetchingFollowRequests') @@ -799,9 +825,9 @@ const users = { useInterfaceStore().setLayoutHeight(windowHeight()) // Fetch our friends - store.rootState.api.backendInteractor - .fetchFriends({ id: user.id }) - .then((friends) => commit('addNewUsers', friends)) + 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 new file mode 100644 index 000000000..975a7386e --- /dev/null +++ b/src/services/api/admin.js @@ -0,0 +1,509 @@ +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 5ab1fa18e..1aeeaa603 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,7 +9,9 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { RegistrationError, StatusCodeError } from '../errors/errors' +import { promisedRequest } from './helpers.js' + +import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' /* eslint-env browser */ const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' @@ -116,12 +118,6 @@ 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` @@ -131,185 +127,25 @@ const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` -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' - -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) => +const 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 }) => { +export const updateNotificationSettings = ({ credentials, settings }) => { const form = new FormData() each(settings, (value, key) => { form.append(key, value) }) - return fetch( - `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, - { - headers: authHeaders(credentials), - method: 'PUT', - body: form, - }, - ).then((data) => data.json()) + return promisedRequest({ + url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, + credentials, + method: 'PUT', + formData: form, + }) } -const updateProfileImages = ({ +export const updateProfileImages = ({ credentials, avatar = null, avatarName = null, @@ -326,21 +162,20 @@ const updateProfileImages = ({ } if (banner !== null) form.append('header', banner) if (background !== null) form.append('pleroma_background_image', background) - return fetch(MASTODON_PROFILE_UPDATE_URL, { - headers: authHeaders(credentials), + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, method: 'PATCH', - body: form, + formData: form, + }).then((data) => { + if (data.error) { + throw new Error(data.error) + } + return parseUser(data) }) - .then((data) => data.json()) - .then((data) => { - if (data.error) { - throw new Error(data.error) - } - return parseUser(data) - }) } -const updateProfile = ({ credentials, params }) => { +export const updateProfile = ({ credentials, params }) => { const formData = new FormData() for (const name in params) { @@ -360,23 +195,21 @@ const updateProfile = ({ credentials, params }) => { } } - return fetch(MASTODON_PROFILE_UPDATE_URL, { - headers: authHeaders(credentials), + return promisedRequest({ + url: MASTODON_PROFILE_UPDATE_URL, + credentials, method: 'PATCH', - body: formData, - }) - .then((data) => data.json()) - .then((data) => parseUser(data)) + formData, + }).then((data) => parseUser(data)) } -const updateProfileJSON = ({ credentials, params }) => { - return promisedRequest({ +export const updateProfileJSON = ({ credentials, params }) => + promisedRequest({ url: MASTODON_PROFILE_UPDATE_URL, credentials, payload: params, method: 'PATCH', }).then((data) => parseUser(data)) -} // Params needed: // nickname @@ -391,109 +224,87 @@ const updateProfileJSON = ({ credentials, params }) => { // location // token // language -const register = ({ params, credentials }) => { +export const register = ({ params, credentials }) => { const { nickname, ...rest } = params - return fetch(MASTODON_REGISTRATION_URL, { + return promisedRequest({ + url: MASTODON_REGISTRATION_URL, method: 'POST', - headers: { - ...authHeaders(credentials), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ + credentials, + payload: { 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) - }) - } + }, }) } -const getCaptcha = () => - fetch('/api/pleroma/captcha').then((resp) => resp.json()) +export const getCaptcha = () => + promisedRequest({ + url: '/api/pleroma/captcha', + }) -const authHeaders = (accessToken) => { - if (accessToken) { - return { Authorization: `Bearer ${accessToken}` } - } else { - return {} - } -} - -const followUser = ({ id, credentials, ...options }) => { - const url = MASTODON_FOLLOW_URL(id) +export const followUser = ({ id, credentials, ...options }) => { const form = {} + if (options.reblogs !== undefined) { form.reblogs = options.reblogs } + if (options.notify !== undefined) { form.notify = options.notify } - return fetch(url, { - body: JSON.stringify(form), - headers: { - ...authHeaders(credentials), - 'Content-Type': 'application/json', - }, - method: 'POST', - }).then((data) => data.json()) -} -const unfollowUser = ({ id, credentials }) => { - const url = MASTODON_UNFOLLOW_URL(id) - return fetch(url, { - headers: authHeaders(credentials), - method: 'POST', - }).then((data) => data.json()) -} - -const fetchUserInLists = ({ id, credentials }) => { - const url = MASTODON_USER_IN_LISTS(id) - return fetch(url, { - headers: authHeaders(credentials), - }).then((data) => data.json()) -} - -const pinOwnStatus = ({ id, credentials }) => { return promisedRequest({ + url: MASTODON_FOLLOW_URL(id), + formData: form, + credentials, + method: 'POST', + }) +} + +export const unfollowUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNFOLLOW_URL(id), + credentials, + method: 'POST', + }) + +export const fetchUserInLists = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_USER_IN_LISTS(id), + credentials, + }) + +export const pinOwnStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const unpinOwnStatus = ({ id, credentials }) => { - return promisedRequest({ +export const unpinOwnStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNPIN_OWN_STATUS(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const muteConversation = ({ id, credentials }) => { - return promisedRequest({ +export const muteConversation = ({ id, credentials }) => + promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const unmuteConversation = ({ id, credentials }) => { - return promisedRequest({ +export const unmuteConversation = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST', }).then((data) => parseStatus(data)) -} -const blockUser = ({ id, expiresIn, credentials }) => { +export const blockUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.duration = expiresIn @@ -507,22 +318,22 @@ const blockUser = ({ id, expiresIn, credentials }) => { }) } -const unblockUser = ({ id, credentials }) => { - return fetch(MASTODON_UNBLOCK_USER_URL(id), { - headers: authHeaders(credentials), +export const unblockUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_UNBLOCK_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const removeUserFromFollowers = ({ id, credentials }) => { - return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), { - headers: authHeaders(credentials), +export const removeUserFromFollowers = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const editUserNote = ({ id, credentials, comment }) => { - return promisedRequest({ +export const editUserNote = ({ id, credentials, comment }) => + promisedRequest({ url: MASTODON_USER_NOTE_URL(id), credentials, payload: { @@ -530,31 +341,29 @@ const editUserNote = ({ id, credentials, comment }) => { }, method: 'POST', }) -} -const approveUser = ({ id, credentials }) => { - const url = MASTODON_APPROVE_USER_URL(id) - return fetch(url, { - headers: authHeaders(credentials), +export const approveUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_APPROVE_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const denyUser = ({ id, credentials }) => { - const url = MASTODON_DENY_USER_URL(id) - return fetch(url, { - headers: authHeaders(credentials), +export const denyUser = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_DENY_USER_URL(id), + credentials, method: 'POST', - }).then((data) => data.json()) -} + }) -const fetchUser = ({ id, credentials }) => { - const url = `${MASTODON_USER_URL}/${id}` - return promisedRequest({ url, credentials }).then((data) => parseUser(data)) -} +export const fetchUser = ({ id, credentials }) => + promisedRequest({ + url: `${MASTODON_USER_URL}/${id}`, + credentials, + }).then((data) => parseUser(data)) -const fetchUserByName = ({ name, credentials }) => { - return promisedRequest({ +export const fetchUserByName = ({ name, credentials }) => + promisedRequest({ url: MASTODON_USER_LOOKUP_URL, credentials, params: { acct: name }, @@ -570,25 +379,20 @@ const fetchUserByName = ({ name, credentials }) => { } }) .then((id) => fetchUser({ 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 fetchUserRelationship = ({ id, credentials }) => + promisedRequest({ + url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, + credentials, }) -} -const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { +export const fetchFriends = ({ + id, + maxId, + sinceId, + limit = 20, + credentials, +}) => { let url = MASTODON_FOLLOWING_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -600,12 +404,13 @@ const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => { .join('&') url = url + (args ? '?' + args : '') - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) + return promisedRequest({ + url, + credentials, + }).then((data) => data.map(parseUser)) } -const exportFriends = ({ id, credentials }) => { +export const exportFriends = ({ id, credentials }) => { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this return new Promise(async (resolve, reject) => { try { @@ -626,7 +431,13 @@ const exportFriends = ({ id, credentials }) => { }) } -const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { +export const fetchFollowers = ({ + id, + maxId, + sinceId, + limit = 20, + credentials, +}) => { let url = MASTODON_FOLLOWERS_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -638,263 +449,122 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => { .join('&') url += args ? '?' + args : '' - return fetch(url, { headers: authHeaders(credentials) }) - .then((data) => data.json()) - .then((data) => data.map(parseUser)) + return promisedRequest({ + 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 fetchFollowRequests = ({ credentials }) => + promisedRequest({ + url: MASTODON_FOLLOW_REQUESTS_URL, + credentials, + }).then((data) => data.map(parseUser)) -const fetchLists = ({ credentials }) => { - const url = MASTODON_LISTS_URL - return fetch(url, { headers: authHeaders(credentials) }).then((data) => - data.json(), - ) -} +export const fetchLists = ({ 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, +export const createList = ({ title, credentials }) => + promisedRequest({ + url: MASTODON_LISTS_URL, + credentials, method: 'POST', - body: JSON.stringify({ title }), - }).then((data) => data.json()) -} + payload: { title }, + }) -const getList = ({ listId, credentials }) => { - const url = MASTODON_LIST_URL(listId) - return fetch(url, { headers: authHeaders(credentials) }).then((data) => - data.json(), - ) -} +export const getList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), + credentials, + }) -const updateList = ({ listId, title, credentials }) => { - const url = MASTODON_LIST_URL(listId) - const headers = authHeaders(credentials) - headers['Content-Type'] = 'application/json' +export const updateList = ({ listId, title, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), - return fetch(url, { - headers, + credentials, method: 'PUT', - body: JSON.stringify({ title }), + payload: { title }, }) -} -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 getListAccounts = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, + }).then((data) => data.map(({ id }) => id)) -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, +export const addAccountsToList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, method: 'POST', - body: JSON.stringify({ account_ids: accountIds }), + payload: { account_ids: accountIds }, }) -} -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, +export const removeAccountsFromList = ({ listId, accountIds, credentials }) => + promisedRequest({ + url: MASTODON_LIST_ACCOUNTS_URL(listId), + credentials, method: 'DELETE', - body: JSON.stringify({ account_ids: accountIds }), + payload: { account_ids: accountIds }, }) -} -const deleteList = ({ listId, credentials }) => { - const url = MASTODON_LIST_URL(listId) - return fetch(url, { +export const deleteList = ({ listId, credentials }) => + promisedRequest({ + url: MASTODON_LIST_URL(listId), method: 'DELETE', - headers: authHeaders(credentials), + 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()) +export const fetchConversation = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_CONTEXT_URL(id), + credentials, + }) .then(({ ancestors, descendants }) => ({ ancestors: ancestors.map(parseStatus), descendants: descendants.map(parseStatus), })) -} - -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 }) + .catch((error) => { + throw new Error('Error fetching timeline', error) }) - .then((data) => data.json()) + +export const fetchStatus = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_URL(id), + credentials, + }) .then((data) => parseStatus(data)) -} - -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 }) + .catch((error) => { + throw new Error('Error fetching timeline', error) }) - .then((data) => data.json()) - .then((data) => parseSource(data)) -} -const fetchStatusHistory = ({ status, credentials }) => { - const url = MASTODON_STATUS_HISTORY_URL(status.id) - return promisedRequest({ url, credentials }).then((data) => { +export const fetchStatusSource = ({ id, credentials }) => + promisedRequest({ + url: MASTODON_STATUS_SOURCE_URL(id), + credentials, + }) + .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) => { data.reverse() return data.map((item) => { item.originalStatus = status return parseStatus(item) }) }) -} -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 = ({ +export const fetchTimeline = ({ timeline, credentials, since = false, @@ -989,109 +659,87 @@ const fetchTimeline = ({ ) url += `?${queryString}` - return fetch(url, { headers: authHeaders(credentials) }).then( - async (response) => { - const success = response.ok - - 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 - } - }, - ) -} - -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))) -} - -const favorite = ({ id, credentials }) => { return promisedRequest({ + url, + credentials, + }).then(async (data) => { + const pagination = parseLinkHeaderPagination( + data._response.headers.get('Link'), + { + flakeId: timeline !== 'bookmarks' && timeline !== 'notifications', + }, + ) + + return { + data: data.map(isNotifications ? parseNotification : parseStatus), + pagination, + } + }) +} + +export const listEmojiPacks = ({ page, pageSize, credentials }) => + promisedRequest({ + url: EMOJI_PACKS_URL(page, pageSize), + }) + +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({ url: MASTODON_FAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const unfavorite = ({ id, credentials }) => { - return promisedRequest({ +export const unfavorite = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNFAVORITE_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const retweet = ({ id, credentials }) => { - return promisedRequest({ +export const retweet = ({ id, credentials }) => + promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const unretweet = ({ id, credentials }) => { - return promisedRequest({ +export const unretweet = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNRETWEET_URL(id), method: 'POST', credentials, }).then((data) => parseStatus(data)) -} -const bookmarkStatus = ({ id, credentials, ...options }) => { - return promisedRequest({ +export const bookmarkStatus = ({ id, credentials, ...options }) => + promisedRequest({ url: MASTODON_BOOKMARK_STATUS_URL(id), - headers: authHeaders(credentials), + credentials, method: 'POST', payload: { folder_id: options.folder_id, }, }) -} -const unbookmarkStatus = ({ id, credentials }) => { - return promisedRequest({ +export const unbookmarkStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNBOOKMARK_STATUS_URL(id), - headers: authHeaders(credentials), + credentials, method: 'POST', }) -} -const postStatus = ({ +export const postStatus = ({ credentials, status, spoilerText, @@ -1140,23 +788,21 @@ const postStatus = ({ form.append('preview', 'true') } - const postHeaders = authHeaders(credentials) + const headers = {} if (idempotencyKey) { - postHeaders['idempotency-key'] = idempotencyKey + headers['idempotency-key'] = idempotencyKey } - return fetch(MASTODON_POST_STATUS_URL, { - body: form, + return promisedRequest({ + url: MASTODON_POST_STATUS_URL, + formData: form, method: 'POST', - headers: postHeaders, - }) - .then((response) => { - return response.json() - }) - .then((data) => (data.error ? data : parseStatus(data))) + credentials, + headers, + }).then((data) => (data.error ? data : parseStatus(data))) } -const editStatus = ({ +export const editStatus = ({ id, credentials, status, @@ -1191,136 +837,131 @@ const editStatus = ({ }) } - const putHeaders = authHeaders(credentials) - - return fetch(MASTODON_STATUS_URL(id), { - body: form, + return promisedRequest({ + url: MASTODON_STATUS_URL(id), + formData: form, method: 'PUT', - headers: putHeaders, - }) - .then((response) => { - return response.json() - }) - .then((data) => (data.error ? data : parseStatus(data))) + credentials, + }).then((data) => (data.error ? data : parseStatus(data))) } -const deleteStatus = ({ id, credentials }) => { - return promisedRequest({ +export const deleteStatus = ({ id, credentials }) => + promisedRequest({ url: MASTODON_DELETE_URL(id), credentials, method: 'DELETE', }) -} -const uploadMedia = ({ formData, credentials }) => { - return fetch(MASTODON_MEDIA_UPLOAD_URL, { - body: formData, +export const uploadMedia = ({ formData, credentials }) => + promisedRequest({ + url: MASTODON_MEDIA_UPLOAD_URL, + formData, method: 'POST', - headers: authHeaders(credentials), - }) - .then((data) => data.json()) - .then((data) => parseAttachment(data)) -} + credentials, + }).then((data) => parseAttachment(data)) -const setMediaDescription = ({ id, description, credentials }) => { - return promisedRequest({ +export const setMediaDescription = ({ id, description, credentials }) => + promisedRequest({ url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`, method: 'PUT', - headers: authHeaders(credentials), + credentials, payload: { description, }, }).then((data) => parseAttachment(data)) -} -const importMutes = ({ file, credentials }) => { +export const importMutes = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(MUTES_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: MUTES_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const importBlocks = ({ file, credentials }) => { +export const importBlocks = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(BLOCKS_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: BLOCKS_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const importFollows = ({ file, credentials }) => { +export const importFollows = ({ file, credentials }) => { const formData = new FormData() formData.append('list', file) - return fetch(FOLLOW_IMPORT_URL, { - body: formData, + return promisedRequest({ + url: FOLLOW_IMPORT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), + credentials, }).then((response) => response.ok) } -const deleteAccount = ({ credentials, password }) => { - const form = new FormData() +export const deleteAccount = ({ credentials, password }) => { + const formData = new FormData() - form.append('password', password) + formData.append('password', password) - return fetch(DELETE_ACCOUNT_URL, { - body: form, + return promisedRequest({ + url: DELETE_ACCOUNT_URL, + formData, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const changeEmail = ({ credentials, email, password }) => { +export const changeEmail = ({ credentials, email, password }) => { const form = new FormData() form.append('email', email) form.append('password', password) - return fetch(CHANGE_EMAIL_URL, { - body: form, + return promisedRequest({ + url: CHANGE_EMAIL_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const moveAccount = ({ credentials, password, targetAccount }) => { +export const moveAccount = ({ credentials, password, targetAccount }) => { const form = new FormData() form.append('password', password) form.append('target_account', targetAccount) - return fetch(MOVE_ACCOUNT_URL, { - body: form, + return promisedRequest({ + url: MOVE_ACCOUNT_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const addAlias = ({ credentials, alias }) => { - return promisedRequest({ +export const addAlias = ({ credentials, alias }) => + promisedRequest({ url: ALIASES_URL, method: 'PUT', credentials, payload: { alias }, }) -} -const deleteAlias = ({ credentials, alias }) => { - return promisedRequest({ +export const deleteAlias = ({ credentials, alias }) => + promisedRequest({ url: ALIASES_URL, method: 'DELETE', credentials, payload: { alias }, }) -} -const listAliases = ({ credentials }) => { - return promisedRequest({ +export const listAliases = ({ credentials }) => + promisedRequest({ url: ALIASES_URL, method: 'GET', credentials, @@ -1328,9 +969,8 @@ const listAliases = ({ credentials }) => { _cacheBooster: new Date().getTime(), }, }) -} -const changePassword = ({ +export const changePassword = ({ credentials, password, newPassword, @@ -1342,58 +982,61 @@ const changePassword = ({ form.append('new_password', newPassword) form.append('new_password_confirmation', newPasswordConfirmation) - return fetch(CHANGE_PASSWORD_URL, { - body: form, + return promisedRequest({ + url: CHANGE_PASSWORD_URL, + formData: form, method: 'POST', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const settingsMFA = ({ credentials }) => { - return fetch(MFA_SETTINGS_URL, { - headers: authHeaders(credentials), +export const settingsMFA = ({ credentials }) => + promisedRequest({ + url: MFA_SETTINGS_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} + }) -const mfaDisableOTP = ({ credentials, password }) => { +export const mfaDisableOTP = ({ credentials, password }) => { const form = new FormData() form.append('password', password) - return fetch(MFA_DISABLE_OTP_URL, { - body: form, + return promisedRequest({ + url: MFA_DISABLE_OTP_URL, + formData: form, method: 'DELETE', - headers: authHeaders(credentials), - }).then((response) => response.json()) + credentials, + }) } -const mfaConfirmOTP = ({ credentials, password, token }) => { +export const mfaConfirmOTP = ({ credentials, password, token }) => { const form = new FormData() form.append('password', password) form.append('code', token) - return fetch(MFA_CONFIRM_OTP_URL, { - body: form, - headers: authHeaders(credentials), + return promisedRequest({ + url: MFA_CONFIRM_OTP_URL, + formData: form, + credentials, method: 'POST', - }).then((data) => data.json()) + }) } -const mfaSetupOTP = ({ credentials }) => { - return fetch(MFA_SETUP_OTP_URL, { - headers: authHeaders(credentials), +export const mfaSetupOTP = ({ credentials }) => + promisedRequest({ + url: MFA_SETUP_OTP_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} -const generateMfaBackupCodes = ({ credentials }) => { - return fetch(MFA_BACKUP_CODES_URL, { - headers: authHeaders(credentials), + }) +export const generateMfaBackupCodes = ({ credentials }) => + promisedRequest({ + url: MFA_BACKUP_CODES_URL, + credentials, method: 'GET', - }).then((data) => data.json()) -} + }) -const fetchMutes = ({ maxId, credentials }) => { +export const fetchMutes = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1404,7 +1047,7 @@ const fetchMutes = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -const muteUser = ({ id, expiresIn, credentials }) => { +export const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} if (expiresIn) { payload.expires_in = expiresIn @@ -1418,15 +1061,14 @@ const muteUser = ({ id, expiresIn, credentials }) => { }) } -const unmuteUser = ({ id, credentials }) => { - return promisedRequest({ +export const unmuteUser = ({ id, credentials }) => + promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST', }) -} -const fetchBlocks = ({ maxId, credentials }) => { +export const fetchBlocks = ({ maxId, credentials }) => { const query = new URLSearchParams({ with_relationships: true }) if (maxId) { query.append('max_id', maxId) @@ -1437,16 +1079,15 @@ const fetchBlocks = ({ maxId, credentials }) => { }).then((users) => users.map(parseUser)) } -const addBackup = ({ credentials }) => { - return promisedRequest({ +export const addBackup = ({ credentials }) => + promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'POST', credentials, }) -} -const listBackups = ({ credentials }) => { - return promisedRequest({ +export const listBackups = ({ credentials }) => + promisedRequest({ url: PLEROMA_BACKUP_URL, method: 'GET', credentials, @@ -1454,53 +1095,48 @@ const listBackups = ({ credentials }) => { _cacheBooster: new Date().getTime(), }, }) -} -const fetchOAuthTokens = ({ credentials }) => { - const url = '/api/oauth_tokens.json' - - return fetch(url, { - headers: authHeaders(credentials), - }).then((data) => { - if (data.ok) { - return data.json() - } - throw new Error('Error fetching auth tokens', data) +export const fetchOAuthTokens = ({ credentials }) => + promisedRequest({ + url: '/api/oauth_tokens.json', + credentials, }) -} -const revokeOAuthToken = ({ id, credentials }) => { - const url = `/api/oauth_tokens/${id}` - - return fetch(url, { - headers: authHeaders(credentials), +export const revokeOAuthToken = ({ id, credentials }) => + promisedRequest({ + url: `/api/oauth_tokens/${id}`, + credentials, method: 'DELETE', }) -} -const suggestions = ({ credentials }) => { - return fetch(SUGGESTIONS_URL, { - headers: authHeaders(credentials), - }).then((data) => data.json()) -} +export const suggestions = ({ credentials }) => + promisedRequest({ + url: SUGGESTIONS_URL, + credentials, + }) -const markNotificationsAsSeen = ({ id, credentials, single = false }) => { - const body = new FormData() +export const markNotificationsAsSeen = ({ + id, + credentials, + single = false, +}) => { + const formData = new FormData() if (single) { - body.append('id', id) + formData.append('id', id) } else { - body.append('max_id', id) + formData.append('max_id', id) } - return fetch(NOTIFICATION_READ_URL, { - body, - headers: authHeaders(credentials), + return promisedRequest({ + url: NOTIFICATION_READ_URL, + formData, + credentials, method: 'POST', - }).then((data) => data.json()) + }) } -const vote = ({ pollId, choices, credentials }) => { +export const vote = ({ pollId, choices, credentials }) => { const form = new FormData() form.append('choices', choices) @@ -1514,32 +1150,29 @@ const vote = ({ pollId, choices, credentials }) => { }) } -const fetchPoll = ({ pollId, credentials }) => { - return promisedRequest({ +export const fetchPoll = ({ pollId, credentials }) => + promisedRequest({ url: MASTODON_POLL_URL(encodeURIComponent(pollId)), method: 'GET', credentials, }) -} -const fetchFavoritedByUsers = ({ id, credentials }) => { - return promisedRequest({ +export const fetchFavoritedByUsers = ({ id, credentials }) => + promisedRequest({ url: MASTODON_STATUS_FAVORITEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) -} -const fetchRebloggedByUsers = ({ id, credentials }) => { - return promisedRequest({ +export const fetchRebloggedByUsers = ({ id, credentials }) => + promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id), method: 'GET', credentials, }).then((users) => users.map(parseUser)) -} -const fetchEmojiReactions = ({ id, credentials }) => { - return promisedRequest({ +export const fetchEmojiReactions = ({ id, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials, }).then((reactions) => @@ -1548,26 +1181,29 @@ const fetchEmojiReactions = ({ id, credentials }) => { return r }), ) -} -const reactWithEmoji = ({ id, emoji, credentials }) => { - return promisedRequest({ +export const reactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_REACT_URL(id, emoji), method: 'PUT', credentials, }).then(parseStatus) -} -const unreactWithEmoji = ({ id, emoji, credentials }) => { - return promisedRequest({ +export const unreactWithEmoji = ({ id, emoji, credentials }) => + promisedRequest({ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji), method: 'DELETE', credentials, }).then(parseStatus) -} -const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { - return promisedRequest({ +export const reportUser = ({ + credentials, + userId, + statusIds, + comment, + forward, +}) => + promisedRequest({ url: MASTODON_REPORT_USER_URL, method: 'POST', payload: { @@ -1578,10 +1214,9 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => { }, credentials, }) -} -const searchUsers = ({ credentials, query }) => { - return promisedRequest({ +export const searchUsers = ({ credentials, query }) => + promisedRequest({ url: MASTODON_USER_SEARCH_URL, params: { q: query, @@ -1589,9 +1224,8 @@ const searchUsers = ({ credentials, query }) => { }, credentials, }).then((data) => data.map(parseUser)) -} -const search2 = ({ +export const search2 = ({ credentials, q, resolve, @@ -1634,214 +1268,59 @@ const search2 = ({ ) url += `?${queryString}` - 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() - }) + return promisedRequest({ + url, + credentials, + }) .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) + }) } -const fetchKnownDomains = ({ credentials }) => { - return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -} +export const fetchKnownDomains = ({ credentials }) => + promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials }) -const fetchDomainMutes = ({ credentials }) => { - return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) -} +export const fetchDomainMutes = ({ credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials }) -const muteDomain = ({ domain, credentials }) => { - return promisedRequest({ +export const muteDomain = ({ domain, credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'POST', payload: { domain }, credentials, }) -} -const unmuteDomain = ({ domain, credentials }) => { - return promisedRequest({ +export const unmuteDomain = ({ domain, credentials }) => + promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, method: 'DELETE', payload: { domain }, credentials, }) -} -const dismissNotification = ({ credentials, id }) => { - return promisedRequest({ +export const dismissNotification = ({ credentials, id }) => + promisedRequest({ url: MASTODON_DISMISS_NOTIFICATION_URL(id), method: 'POST', payload: { id }, credentials, }) -} -const adminFetchAnnouncements = ({ credentials }) => { - return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials }) -} +export const getAnnouncements = ({ credentials }) => + promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) -const fetchAnnouncements = ({ credentials }) => { - return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials }) -} - -const dismissAnnouncement = ({ id, credentials }) => { - return promisedRequest({ +export const dismissAnnouncement = ({ id, credentials }) => + 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 = {} }, @@ -2018,23 +1497,28 @@ export const WSConnectionStatus = Object.freeze({ STARTING_INITIAL: 6, }) -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 chats = ({ credentials }) => + promisedRequest({ + url: PLEROMA_CHATS_URL, + credentials, + }).then((data) => ({ + chatList: data.map(parseChat).filter((c) => c), + })) -const getOrCreateChat = ({ accountId, credentials }) => { - return promisedRequest({ +export const getOrCreateChat = ({ accountId, credentials }) => + promisedRequest({ url: PLEROMA_CHAT_URL(accountId), method: 'POST', credentials, }) -} -const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { +export const chatMessages = ({ + id, + credentials, + maxId, + sinceId, + limit = 20, +}) => { let url = PLEROMA_CHAT_MESSAGES_URL(id) const args = [ maxId && `max_id=${maxId}`, @@ -2053,7 +1537,7 @@ const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => { }) } -const sendChatMessage = ({ +export const sendChatMessage = ({ id, content, mediaId = null, @@ -2083,8 +1567,8 @@ const sendChatMessage = ({ }) } -const readChat = ({ id, lastReadId, credentials }) => { - return promisedRequest({ +export const readChat = ({ id, lastReadId, credentials }) => + promisedRequest({ url: PLEROMA_CHAT_READ_URL(id), method: 'POST', payload: { @@ -2092,436 +1576,49 @@ const readChat = ({ id, lastReadId, credentials }) => { }, credentials, }) -} -const deleteChatMessage = ({ chatId, messageId, credentials }) => { - return promisedRequest({ +export const deleteChatMessage = ({ chatId, messageId, credentials }) => + promisedRequest({ url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId), method: 'DELETE', credentials, }) -} -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 }) => { +export 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 fetch(url, {}).then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response, - } - } + return promisedRequest({ url }) +} + +export const fetchBookmarkFolders = ({ credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, }) -} -const deleteEmojiPack = ({ name }) => { - return fetch(PLEROMA_EMOJI_PACK_URL(name), { method: 'DELETE' }) -} - -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, { +export const createBookmarkFolder = ({ name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDERS_URL, + credentials, method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - url: instance, - name: packName, - as, - }), + payload: { name, emoji }, }) -} -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), { +export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), + credentials, method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ metadata: newData }), + payload: { name, emoji }, }) -} -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, { +export const deleteBookmarkFolder = ({ folderId, credentials }) => + promisedRequest({ + url: PLEROMA_BOOKMARK_FOLDER_URL(folderId), method: 'DELETE', - headers: authHeaders(credentials), + 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 new file mode 100644 index 000000000..80012cca3 --- /dev/null +++ b/src/services/api/helpers.js @@ -0,0 +1,87 @@ +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 deleted file mode 100644 index adc18ef7f..000000000 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index 7b81c19dc..000000000 --- a/src/services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js +++ /dev/null @@ -1,32 +0,0 @@ -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 209a5f0c0..c754c7316 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -1,8 +1,18 @@ +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(() => { - store.state.api.backendInteractor - .fetchUserRelationship({ id: userId }) + fetchUserRelationship({ + id: userId, + credentials: useOAuthStore().token, + }) .then((relationship) => { store.commit('updateUserRelationship', [relationship]) return relationship @@ -27,38 +37,40 @@ const fetchRelationship = (attempt, userId, store) => export const requestFollow = (userId, store) => new Promise((resolve) => { - store.state.api.backendInteractor - .followUser({ id: userId }) - .then((updated) => { - store.commit('updateUserRelationship', [updated]) + followUser({ + id: userId, + credentials: useOAuthStore().token, + }).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) => { - store.state.api.backendInteractor - .unfollowUser({ id: userId }) - .then((updated) => { - store.commit('updateUserRelationship', [updated]) - resolve({ - updated, - }) + unfollowUser({ + id: userId, + credentials: useOAuthStore().token, + }).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 530c98aa7..015eb55ed 100644 --- a/src/services/follow_request_fetcher/follow_request_fetcher.service.js +++ b/src/services/follow_request_fetcher/follow_request_fetcher.service.js @@ -1,9 +1,8 @@ -import apiService from '../api/api.service.js' +import { fetchFollowRequests } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' const fetchAndUpdate = ({ store, credentials }) => { - return apiService - .fetchFollowRequests({ credentials }) + return 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 deleted file mode 100644 index c395ef93b..000000000 --- a/src/services/lists_fetcher/lists_fetcher.service.js +++ /dev/null @@ -1,32 +0,0 @@ -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 c1a9e1a2f..ec6c47a5d 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -1,4 +1,4 @@ -import apiService from '../api/api.service.js' +import { fetchTimeline } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -80,8 +80,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { } const fetchNotifications = ({ store, args, older }) => { - return apiService - .fetchTimeline(args) + return 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 46ac68996..b28b7dc5a 100644 --- a/src/services/promise_interval/promise_interval.js +++ b/src/services/promise_interval/promise_interval.js @@ -4,32 +4,39 @@ // 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) } - timeout = window.setTimeout(func, interval) + 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() return { stop: stopFetcher } } diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 021c31ef8..1a814ec7c 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,6 +1,11 @@ import { map } from 'lodash' -import apiService from '../api/api.service.js' +import { + editStatus as apiEditStatus, + postStatus as apiPostStatus, + setMediaDescription as apiSetMediaDescription, + uploadMedia as apiUploadMedia, +} from '../api/api.service.js' const postStatus = ({ store, @@ -18,21 +23,20 @@ const postStatus = ({ }) => { const mediaIds = map(media, 'id') - return apiService - .postStatus({ - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - visibility, - sensitive, - mediaIds, - inReplyToStatusId, - quoteId, - contentType, - poll, - preview, - idempotencyKey, - }) + return apiPostStatus({ + 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', { @@ -63,17 +67,16 @@ const editStatus = ({ }) => { const mediaIds = map(media, 'id') - return apiService - .editStatus({ - id: statusId, - credentials: store.state.users.currentUser.credentials, - status, - spoilerText, - sensitive, - poll, - mediaIds, - contentType, - }) + return editStatus({ + id: statusId, + credentials: store.state.users.currentUser.credentials, + status, + spoilerText, + sensitive, + poll, + mediaIds, + contentType, + }) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { @@ -95,12 +98,12 @@ const editStatus = ({ const uploadMedia = ({ store, formData }) => { const credentials = store.state.users.currentUser.credentials - return apiService.uploadMedia({ credentials, formData }) + return apiUploadMedia({ credentials, formData }) } const setMediaDescription = ({ store, id, description }) => { const credentials = store.state.users.currentUser.credentials - return apiService.setMediaDescription({ credentials, id, description }) + return apiSetMediaDescription({ 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 68991addf..8e9b8a2e9 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 apiService from '../api/api.service.js' +import { fetchTimeline } from '../api/api.service.js' import { promiseInterval } from '../promise_interval/promise_interval.js' import { useInstanceStore } from 'src/stores/instance.js' @@ -75,8 +75,7 @@ const fetchAndUpdate = ({ const numStatusesBeforeFetch = timelineData.statuses.length - return apiService - .fetchTimeline(args) + return 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 b07315034..884ac37a1 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -1,6 +1,38 @@ 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 = { @@ -21,7 +53,6 @@ export const newUserFlags = { export const useAdminSettingsStore = defineStore('adminSettings', { state: () => ({ ...cloneDeep(defaultState), - backendInteractor: window.vuex.state.api.backendInteractor, }), actions: { // Configuration Stuff @@ -54,7 +85,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, loadAdminStuff() { - this.backendInteractor.fetchInstanceDBConfig().then((backendDbConfig) => { + getInstanceDBConfig({ + credentials: useOAuthStore().token, + }).then((backendDbConfig) => { if (backendDbConfig.error) { if (backendDbConfig.error.status === 400) { backendDbConfig.error.json().then((errorJson) => { @@ -64,15 +97,21 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) } } else { - this.setInstanceAdminSettings({ backendDbConfig }) + this.setInstanceAdminSettings({ + credentials: useOAuthStore().token, + backendDbConfig, + }) } }) if (this.descriptions === null) { - this.backendInteractor - .fetchInstanceConfigDescriptions() - .then((backendDescriptions) => - this.setInstanceAdminDescriptions({ backendDescriptions }), - ) + getInstanceConfigDescriptions({ + credentials: useOAuthStore().token, + }).then((backendDescriptions) => + this.setInstanceAdminDescriptions({ + credentials: useOAuthStore().token, + backendDescriptions, + }), + ) } }, setInstanceAdminSettings({ backendDbConfig }) { @@ -203,17 +242,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }) - window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: changed, - }, - }) + pushInstanceDBConfig({ + credentials: useOAuthStore().token, + payload: { + configs: changed, + }, + }) .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), + getInstanceDBConfig({ + credentials: useOAuthStore().token, + }), ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ backendDbConfig }), + this.setInstanceAdminSettings({ + credentials: useOAuthStore().token, + + backendDbConfig, + }), ) }, pushAdminSetting({ path, value }) { @@ -234,23 +279,28 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } } - window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: [ - { - group, - key, - value: convert(clone), - }, - ], - }, - }) + pushInstanceDBConfig({ + credentials: useOAuthStore().token, + payload: { + configs: [ + { + group, + key, + value: convert(clone), + }, + ], + }, + }) .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), + getInstanceDBConfig({ + credentials: useOAuthStore().token, + }), ) .then((backendDbConfig) => - this.setInstanceAdminSettings({ backendDbConfig }), + this.setInstanceAdminSettings({ + credentials: useOAuthStore().token, + backendDbConfig, + }), ) }, resetAdminSetting({ path }) { @@ -260,21 +310,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', { this.modifiedPaths.delete(path) - return window.vuex.state.api.backendInteractor - .pushInstanceDBConfig({ - payload: { - configs: [ - { - group, - key, - delete: true, - subkeys: [subkey], - }, - ], - }, - }) + return pushInstanceDBConfig({ + credentials: useOAuthStore().token, + payload: { + configs: [ + { + group, + key, + delete: true, + subkeys: [subkey], + }, + ], + }, + }) .then(() => - window.vuex.state.api.backendInteractor.fetchInstanceDBConfig(), + getInstanceDBConfig({ + credentials: useOAuthStore().token, + }), ) .then((backendDbConfig) => this.setInstanceAdminSettings({ backendDbConfig }), @@ -283,9 +335,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Frontends Stuff loadFrontendsStuff() { - this.backendInteractor - .fetchAvailableFrontends() - .then((frontends) => this.setAvailableFrontends({ frontends })) + getAvailableFrontends({ + credentials: useOAuthStore().token, + }).then((frontends) => this.setAvailableFrontends({ frontends })) }, setAvailableFrontends({ frontends }) { @@ -300,12 +352,18 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) }, + installFrontend() { + return installFrontend({ + credentials: useOAuthStore().token, + }) + }, + // Statuses stuff async fetchStatuses(opts) { - const { total, activities } = - await this.backendInteractor.adminListStatuses({ - opts, - }) + const { total, activities } = await listStatuses({ + credentials: useOAuthStore().token, + opts, + }) const statuses = activities.map(parseStatus) @@ -317,7 +375,8 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async changeStatusScope(opts) { - const raw = await this.backendInteractor.adminChangeStatusScope({ + const raw = await changeStatusScope({ + credentials: useOAuthStore().token, opts, }) const status = parseStatus(raw) @@ -327,7 +386,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { // Users stuff async fetchUsers(opts) { - const { users, count } = await this.backendInteractor.adminListUsers({ + const { users, count } = await listUsers({ + credentials: useOAuthStore().token, + opts, }) @@ -344,17 +405,23 @@ export const useAdminSettingsStore = defineStore('adminSettings', { } }, async getUserData({ user }) { - const api = this.backendInteractor.adminGetUserData + const api = getUserData const { screen_name } = user - const result = await api({ screen_name }) + const result = await api({ + credentials: useOAuthStore().token, + screen_name, + }) window.vuex.commit('updateUserAdminData', { user: result }) }, async deleteUsers({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminDeleteAccounts + const api = deleteAccounts - const resultUserIds = await api({ screen_names }) + const resultUserIds = await api({ + credentials: useOAuthStore().token, + screen_names, + }) resultUserIds.forEach((userId) => { window.vuex.dispatch( @@ -369,14 +436,16 @@ export const useAdminSettingsStore = defineStore('adminSettings', { resendConfirmationEmail({ users }) { const screen_names = users.map((u) => u.screen_name) - return this.backendInteractor.adminResendConfirmationEmail({ + return resendConfirmationEmail({ + credentials: useOAuthStore().token, screen_names, }) }, requirePasswordChange({ users }) { const screen_names = users.map((u) => u.screen_name) - return this.backendInteractor.adminRequirePasswordChange({ + return requirePasswordChange({ + credentials: useOAuthStore().token, screen_names, }) }, @@ -384,13 +453,17 @@ export const useAdminSettingsStore = defineStore('adminSettings', { disableMFA({ user }) { const { screen_name } = user - return this.backendInteractor.adminDisableMFA({ screen_name }) + return disableMFA({ + credentials: useOAuthStore().token, + screen_name, + }) }, async setUsersTags({ users, tags, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersTags + const api = setUsersTags await api({ + credentials: useOAuthStore().token, screen_names, tags, value, @@ -402,9 +475,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersRight({ users, right, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersRight + const api = setUsersRight await api({ + credentials: useOAuthStore().token, screen_names, right, value, @@ -416,9 +490,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersActivationStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersActivationStatus + const api = setUsersActivationStatus const resultUsers = await api({ + credentials: useOAuthStore().token, screen_names, value, }) @@ -429,9 +504,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersSuggestionStatus({ users, value }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersSuggestionStatus + const api = setUsersSuggestionStatus const resultUsers = await api({ + credentials: useOAuthStore().token, screen_names, value, }) @@ -442,9 +518,12 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersConfirmationStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersConfirmationStatus + const api = setUsersConfirmationStatus - await api({ screen_names }) + await api({ + credentials: useOAuthStore().token, + screen_names, + }) users.forEach((user) => { this.getUserData({ user }) @@ -452,9 +531,10 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }, async setUsersApprovalStatus({ users }) { const screen_names = users.map((u) => u.screen_name) - const api = this.backendInteractor.adminSetUsersApprovalStatus + const api = setUsersApprovalStatus const resultUsers = await api({ + credentials: useOAuthStore().token, screen_names, }) @@ -462,5 +542,66 @@ 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 cb325dadd..87d83813a 100644 --- a/src/stores/announcements.js +++ b/src/stores/announcements.js @@ -1,5 +1,18 @@ 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', { @@ -31,15 +44,19 @@ export const useAnnouncementsStore = defineStore('announcements', { currentUser && currentUser.privileges.has('announcements_manage_announcements') - const getAnnouncements = async () => { + const fetchAnnouncements = async () => { if (!isAdmin) { - return window.vuex.state.api.backendInteractor.fetchAnnouncements() + return getAnnouncements({ + credentials: useOAuthStore().token, + }) } - const all = - await window.vuex.state.api.backendInteractor.adminFetchAnnouncements() - const visible = - await window.vuex.state.api.backendInteractor.fetchAnnouncements() + const all = await adminGetAnnouncements({ + credentials: useOAuthStore().token, + }) + const visible = await getAnnouncements({ + credentials: useOAuthStore().token, + }) const visibleObject = visible.reduce((a, c) => { a[c.id] = c return a @@ -59,7 +76,7 @@ export const useAnnouncementsStore = defineStore('announcements', { return all } - return getAnnouncements() + return fetchAnnouncements() .then((announcements) => { this.announcements = announcements }) @@ -74,17 +91,18 @@ export const useAnnouncementsStore = defineStore('announcements', { }) }, markAnnouncementAsRead(id) { - return window.vuex.state.api.backendInteractor - .dismissAnnouncement({ id }) - .then(() => { - const index = this.announcements.findIndex((a) => a.id === id) + return dismissAnnouncement({ + id, + credentials: useOAuthStore().token, + }).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) { @@ -105,25 +123,35 @@ export const useAnnouncementsStore = defineStore('announcements', { clearInterval(interval) }, postAnnouncement({ content, startsAt, endsAt, allDay }) { - return window.vuex.state.api.backendInteractor - .postAnnouncement({ content, startsAt, endsAt, allDay }) - .then(() => { - return this.fetchAnnouncements() - }) + return postAnnouncement({ + credentials: useOAuthStore().token, + content, + startsAt, + endsAt, + allDay, + }).then(() => { + return this.fetchAnnouncements() + }) }, editAnnouncement({ id, content, startsAt, endsAt, allDay }) { - return window.vuex.state.api.backendInteractor - .editAnnouncement({ id, content, startsAt, endsAt, allDay }) - .then(() => { - return this.fetchAnnouncements() - }) + return editAnnouncement({ + id, + content, + startsAt, + endsAt, + allDay, + credentials: useOAuthStore().token, + }).then(() => { + return this.fetchAnnouncements() + }) }, deleteAnnouncement(id) { - return window.vuex.state.api.backendInteractor - .deleteAnnouncement({ id }) - .then(() => { - return this.fetchAnnouncements() - }) + return deleteAnnouncement({ + id, + credentials: useOAuthStore().token, + }).then(() => { + return this.fetchAnnouncements() + }) }, }, }) diff --git a/src/stores/bookmark_folders.js b/src/stores/bookmark_folders.js index 028322f9d..3e16121b1 100644 --- a/src/stores/bookmark_folders.js +++ b/src/stores/bookmark_folders.js @@ -1,6 +1,16 @@ 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: [], @@ -16,6 +26,23 @@ 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 }, @@ -30,23 +57,31 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', { } }, createBookmarkFolder({ name, emoji }) { - return window.vuex.state.api.backendInteractor - .createBookmarkFolder({ name, emoji }) - .then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return createBookmarkFolder({ + name, + emoji, + credentials: useOAuthStore().token, + }).then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, updateBookmarkFolder({ folderId, name, emoji }) { - return window.vuex.state.api.backendInteractor - .updateBookmarkFolder({ folderId, name, emoji }) - .then((folder) => { - this.setBookmarkFolder(folder) - return folder - }) + return updateBookmarkFolder({ + credentials: useOAuthStore().token, + folderId, + name, + emoji, + }).then((folder) => { + this.setBookmarkFolder(folder) + return folder + }) }, deleteBookmarkFolder({ folderId }) { - window.vuex.state.api.backendInteractor.deleteBookmarkFolder({ folderId }) + deleteBookmarkFolder({ + folderId, + credentials: useOAuthStore().token, + }) remove(this.allFolders, (folder) => folder.id === folderId) }, }, diff --git a/src/stores/emoji.js b/src/stores/emoji.js index e28143300..079dedb9f 100644 --- a/src/stores/emoji.js +++ b/src/stores/emoji.js @@ -2,8 +2,10 @@ 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' @@ -183,13 +185,14 @@ 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, - listFunction, + (params) => + listEmojiPacks({ + ...params, + credentials: useOAuthStore().token, + }), ) this.adminPacksLocalLoading = false }, @@ -206,7 +209,6 @@ export const useEmojiStore = defineStore('emoji', { page: 1, pageSize: 0, }) - .then((data) => data.json()) .then((data) => { if (data.error !== undefined) { return Promise.reject(data.error) @@ -220,15 +222,13 @@ export const useEmojiStore = defineStore('emoji', { instance, page: i, pageSize, - }) - .then((data) => data.json()) - .then((pageData) => { - if (pageData.error !== undefined) { - return Promise.reject(pageData.error) - } + }).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) => { - this.displayError(data) + console.error(data) }) }, diff --git a/src/stores/instance.js b/src/stores/instance.js index 54b3cf43c..2f9bf3c44 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 apiService from '../services/api/api.service.js' +import { fetchKnownDomains } 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 apiService.fetchKnownDomains({ + this.knownDomains = await fetchKnownDomains({ credentials: window.vuex.state.users.currentUser.credentials, }) } catch (e) { diff --git a/src/stores/lists.js b/src/stores/lists.js index b33a119cc..c361b6e5e 100644 --- a/src/stores/lists.js +++ b/src/stores/lists.js @@ -1,8 +1,23 @@ 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: {}, }), @@ -18,34 +33,58 @@ 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 window.vuex.state.api.backendInteractor - .createList({ title }) - .then((list) => { - this.setList({ listId: list.id, title }) - return list - }) + return createList({ + title, + credentials: useOAuthStore().token, + }).then((list) => { + this.setList({ listId: list.id, title }) + return list + }) }, fetchList({ listId }) { - return window.vuex.state.api.backendInteractor - .getList({ listId }) - .then((list) => this.setList({ listId: list.id, title: list.title })) + return getList({ + listId, + credentials: useOAuthStore().token, + }).then((list) => this.setList({ listId: list.id, title: list.title })) }, fetchListAccounts({ listId }) { - return window.vuex.state.api.backendInteractor - .getListAccounts({ listId }) - .then((accountIds) => { - if (!this.allListsObject[listId]) { - this.allListsObject[listId] = { accountIds: [] } - } - this.allListsObject[listId].accountIds = accountIds - }) + return getListAccounts({ + listId, + credentials: useOAuthStore().token, + }).then((accountIds) => { + if (!this.allListsObject[listId]) { + this.allListsObject[listId] = { accountIds: [] } + } + this.allListsObject[listId].accountIds = accountIds + }) }, setList({ listId, title }) { - window.vuex.state.api.backendInteractor.updateList({ listId, title }) + updateList({ + listId, + title, + credentials: useOAuthStore().token, + }) if (!this.allListsObject[listId]) { this.allListsObject[listId] = { accountIds: [] } @@ -68,46 +107,55 @@ export const useListsStore = defineStore('lists', { } this.allListsObject[listId].accountIds = accountIds if (added.length > 0) { - window.vuex.state.api.backendInteractor.addAccountsToList({ + addAccountsToList({ listId, accountIds: added, + credentials: useOAuthStore().token, }) } if (removed.length > 0) { - window.vuex.state.api.backendInteractor.removeAccountsFromList({ + removeAccountsFromList({ listId, accountIds: removed, + credentials: useOAuthStore().token, }) } }, addListAccount({ listId, accountId }) { - 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 - }) + 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 + }) }, removeListAccount({ listId, accountId }) { - 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 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 result - }) + return result + }) }, deleteList({ listId }) { - window.vuex.state.api.backendInteractor.deleteList({ listId }) + deleteList({ + listId, + credentials: useOAuthStore().token, + }) delete this.allListsObject[listId] remove(this.allLists, (list) => list.id === listId) diff --git a/src/stores/oauth.js b/src/stores/oauth.js index 2a79c2fa9..116169b81 100644 --- a/src/stores/oauth.js +++ b/src/stores/oauth.js @@ -41,12 +41,9 @@ export const useOAuthStore = defineStore('oauth', { userToken: false, }), getters: { - getToken() { + token() { 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 ae9b396ac..3f9138cec 100644 --- a/src/stores/oauth_tokens.js +++ b/src/stores/oauth_tokens.js @@ -1,25 +1,33 @@ 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() { - window.vuex.state.api.backendInteractor - .fetchOAuthTokens() - .then((tokens) => { - this.swapTokens(tokens) - }) + fetchOAuthTokens({ + credentials: useOAuthStore().token, + }).then((tokens) => { + this.swapTokens(tokens) + }) }, revokeToken(id) { - window.vuex.state.api.backendInteractor - .revokeOAuthToken({ id }) - .then((response) => { - if (response.status === 201) { - this.swapTokens(this.tokens.filter((token) => token.id !== id)) - } - }) + revokeOAuthToken({ + id, + credentials: useOAuthStore().token, + }).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 aac8a2421..78ccd059b 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -1,6 +1,10 @@ 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 @@ -19,16 +23,17 @@ export const usePollsStore = defineStore('polls', { } }, updateTrackedPoll(pollId) { - window.vuex.state.api.backendInteractor - .fetchPoll({ pollId }) - .then((poll) => { - setTimeout(() => { - if (this.trackedPolls[pollId]) { - this.updateTrackedPoll(pollId) - } - }, 30 * 1000) - this.mergeOrAddPoll(poll) - }) + fetchPoll({ + pollId, + credentials: useOAuthStore().token, + }).then((poll) => { + setTimeout(() => { + if (this.trackedPolls[pollId]) { + this.updateTrackedPoll(pollId) + } + }, 30 * 1000) + this.mergeOrAddPoll(poll) + }) }, trackPoll(pollId) { if (!this.trackedPolls[pollId]) { @@ -50,12 +55,14 @@ export const usePollsStore = defineStore('polls', { } }, votePoll({ pollId, choices }) { - return window.vuex.state.api.backendInteractor - .vote({ pollId, choices }) - .then((poll) => { - this.mergeOrAddPoll(poll) - return poll - }) + return vote({ + pollId, + choices, + credentials: useOAuthStore().token, + }).then((poll) => { + this.mergeOrAddPoll(poll) + return poll + }) }, }, }) diff --git a/src/stores/reports.js b/src/stores/reports.js index d3acebcb4..2fea2e8e6 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -2,6 +2,9 @@ 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: () => ({ @@ -38,18 +41,21 @@ export const useReportsStore = defineStore('reports', { setReportState({ id, state }) { const oldState = this.reports[id].state this.reports[id].state = state - 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 + + 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, }) + 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 3010fc738..348579b72 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -22,6 +22,7 @@ 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 { @@ -31,6 +32,7 @@ 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 @@ -789,7 +791,10 @@ 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 } } - window.vuex.state.api.backendInteractor.updateProfileJSON({ params }) + updateProfileJSON({ + params, + credentials: useOAuthStore().token, + }) }, }, persist: { diff --git a/src/stores/user_highlight.js b/src/stores/user_highlight.js index f41c41628..b2cd680f5 100644 --- a/src/stores/user_highlight.js +++ b/src/stores/user_highlight.js @@ -14,7 +14,10 @@ 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 @@ -344,12 +347,13 @@ export const useUserHighlightStore = defineStore('user_highlight', { const params = { pleroma_settings_store: { user_highlight: this.cache }, } - window.vuex.state.api.backendInteractor - .updateProfileJSON({ params }) - .then((user) => { - this.initUserHighlight(user) - this.dirty = false - }) + updateProfileJSON({ + params, + credentials: useOAuthStore().token, + }).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 8b198420b..36d323575 100644 --- a/test/unit/specs/components/user_profile.spec.js +++ b/test/unit/specs/components/user_profile.spec.js @@ -4,7 +4,6 @@ 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: () => { @@ -53,10 +52,6 @@ const externalProfileStore = createStore({ actions, getters: testGetters, state: { - api: { - fetchers: {}, - backendInteractor: backendInteractorService(''), - }, interface: { browserSupport: '', }, @@ -116,10 +111,6 @@ const localProfileStore = createStore({ actions, getters: testGetters, state: { - api: { - fetchers: {}, - backendInteractor: backendInteractorService(''), - }, interface: { browserSupport: '', },