diff options
author | 2023-01-18 14:45:14 +0100 | |
---|---|---|
committer | 2023-01-18 14:45:14 +0100 | |
commit | 9b139b632098e6741b10fa87ff6224dcb5045947 (patch) | |
tree | c72b5c666ed01db7d1a18e531e5e01e07f504a46 /web/source/settings/lib/api | |
parent | [chore] Change default sqlite busy timeout to 5m (#1352) (diff) | |
download | gotosocial-9b139b632098e6741b10fa87ff6224dcb5045947.tar.xz |
[frogend] Settings refactor (#1318)
* yakshave new form field structure
* fully refactor user profile settings form
* use rtk query api for profile settings
* refactor user post settings
* refactor password change form
* refactor admin settings
* FormWithData structure for user forms
* admin actions refactor
* whitespace
* fix user settings data prop
* remove superfluous logging
* cleanup old code
* refactor federation/suspend (overview, detail)
* mostly abstracted (emoji) checkbox list
* refactor parse-from-toot
* refactor custom-emoji, progress on federation bulk
* loading icon styling to prevent big spinny
* refactor federation import-export interface
* cleanup old files
* [chore] Update/add license headers for 2023
* redux fixes
* text-field exports
* appease the linter
* refactor authentication with RTK Query
* fix login/logout state transition weirdness
* fixes/cleanup
* small linter-related fixes
* add eslint license header check, fix existing files
* remove old code, clarify comment
* clarify suspend on subdomains
* collapse if/else
* fa-fw width info comment
Diffstat (limited to 'web/source/settings/lib/api')
-rw-r--r-- | web/source/settings/lib/api/admin.js | 168 | ||||
-rw-r--r-- | web/source/settings/lib/api/index.js | 193 | ||||
-rw-r--r-- | web/source/settings/lib/api/oauth.js | 127 | ||||
-rw-r--r-- | web/source/settings/lib/api/user.js | 67 |
4 files changed, 0 insertions, 555 deletions
diff --git a/web/source/settings/lib/api/admin.js b/web/source/settings/lib/api/admin.js deleted file mode 100644 index 848772db7..000000000 --- a/web/source/settings/lib/api/admin.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const isValidDomain = require("is-valid-domain"); - -const instance = require("../../redux/reducers/instances").actions; -const admin = require("../../redux/reducers/admin").actions; - -module.exports = function ({ apiCall, getChanges }) { - const adminAPI = { - updateInstance: function updateInstance() { - return function (dispatch, getState) { - return Promise.try(() => { - const state = getState().instances.adminSettings; - - const update = getChanges(state, { - formKeys: ["title", "short_description", "description", "contact_account.username", "email", "terms", "thumbnail_description"], - renamedKeys: { - "email": "contact_email", - "contact_account.username": "contact_username" - }, - fileKeys: ["thumbnail"] - }); - - return dispatch(apiCall("PATCH", "/api/v1/instance", update, "form")); - }).then((data) => { - return dispatch(instance.setInstanceInfo(data)); - }); - }; - }, - - fetchDomainBlocks: function fetchDomainBlocks() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks")); - }).then((data) => { - return dispatch(admin.setBlockedInstances(data)); - }); - }; - }, - - updateDomainBlock: function updateDomainBlock(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - const state = getState().admin.newInstanceBlocks[domain]; - const update = getChanges(state, { - formKeys: ["domain", "obfuscate", "public_comment", "private_comment"], - }); - - return dispatch(apiCall("POST", "/api/v1/admin/domain_blocks", update, "form")); - }).then((block) => { - return Promise.all([ - dispatch(admin.newDomainBlock([domain, block])), - dispatch(admin.setDomainBlock([domain, block])) - ]); - }); - }; - }, - - getEditableDomainBlock: function getEditableDomainBlock(domain) { - return function (dispatch, getState) { - let data = getState().admin.blockedInstances[domain]; - return dispatch(admin.newDomainBlock([domain, data])); - }; - }, - - bulkDomainBlock: function bulkDomainBlock() { - return function (dispatch, getState) { - let invalidDomains = []; - let success = 0; - - return Promise.try(() => { - const state = getState().admin.bulkBlock; - let list = state.list; - let domains; - - let fields = getChanges(state, { - formKeys: ["obfuscate", "public_comment", "private_comment"] - }); - - let defaultDate = new Date().toUTCString(); - - if (list[0] == "[") { - domains = JSON.parse(state.list); - } else { - domains = list.split("\n").map((line_) => { - let line = line_.trim(); - if (line.length == 0) { - return null; - } - - if (!isValidDomain(line, {wildcard: true, allowUnicode: true})) { - invalidDomains.push(line); - return null; - } - - return { - domain: line, - created_at: defaultDate, - ...fields - }; - }).filter((a) => a != null); - } - - if (domains.length == 0) { - return; - } - - const update = { - domains: new Blob([JSON.stringify(domains)], {type: "application/json"}) - }; - - return dispatch(apiCall("POST", "/api/v1/admin/domain_blocks?import=true", update, "form")); - }).then((blocks) => { - if (blocks != undefined) { - return Promise.each(blocks, (block) => { - success += 1; - return dispatch(admin.setDomainBlock([block.domain, block])); - }); - } - }).then(() => { - return { - success, - invalidDomains - }; - }); - }; - }, - - removeDomainBlock: function removeDomainBlock(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - const id = getState().admin.blockedInstances[domain].id; - return dispatch(apiCall("DELETE", `/api/v1/admin/domain_blocks/${id}`)); - }).then((removed) => { - return dispatch(admin.removeDomainBlock(removed.domain)); - }); - }; - }, - - mediaCleanup: function mediaCleanup(days) { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("POST", `/api/v1/admin/media_cleanup?remote_cache_days=${days}`)); - }); - }; - }, - }; - return adminAPI; -};
\ No newline at end of file diff --git a/web/source/settings/lib/api/index.js b/web/source/settings/lib/api/index.js deleted file mode 100644 index 89f12cc80..000000000 --- a/web/source/settings/lib/api/index.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const { isPlainObject } = require("is-plain-object"); -const d = require("dotty"); - -const { APIError, AuthenticationError } = require("../errors"); -const { setInstanceInfo, setNamedInstanceInfo } = require("../../redux/reducers/instances").actions; - -function apiCall(method, route, payload, type = "json") { - return function (dispatch, getState) { - const state = getState(); - let base = state.oauth.instance; - let auth = state.oauth.token; - - return Promise.try(() => { - let url = new URL(base); - let [path, query] = route.split("?"); - url.pathname = path; - if (query != undefined) { - url.search = query; - } - let body; - - let headers = { - "Accept": "application/json", - }; - - if (payload != undefined) { - if (type == "json") { - headers["Content-Type"] = "application/json"; - body = JSON.stringify(payload); - } else if (type == "form") { - body = convertToForm(payload); - } - } - - if (auth != undefined) { - headers["Authorization"] = auth; - } - - return fetch(url.toString(), { - method, - headers, - body - }); - }).then((res) => { - // try parse json even with error - let json = res.json().catch((e) => { - throw new APIError(`JSON parsing error: ${e.message}`); - }); - - return Promise.all([res, json]); - }).then(([res, json]) => { - if (!res.ok) { - if (auth != undefined && (res.status == 401 || res.status == 403)) { - // stored access token is invalid - throw new AuthenticationError("401: Authentication error", {json, status: res.status}); - } else { - throw new APIError(json.error, { json }); - } - } else { - return json; - } - }); - }; -} - -/* - Takes an object with (nested) keys, and transforms it into - a FormData object to be sent over the API -*/ -function convertToForm(payload) { - const formData = new FormData(); - Object.entries(payload).forEach(([key, val]) => { - if (isPlainObject(val)) { - Object.entries(val).forEach(([key2, val2]) => { - if (val2 != undefined) { - formData.set(`${key}[${key2}]`, val2); - } - }); - } else { - if (val != undefined) { - formData.set(key, val); - } - } - }); - return formData; -} - -function getChanges(state, keys) { - const { formKeys = [], fileKeys = [], renamedKeys = {} } = keys; - const update = {}; - - formKeys.forEach((key) => { - let value = d.get(state, key); - if (value == undefined) { - return; - } - if (renamedKeys[key]) { - key = renamedKeys[key]; - } - d.put(update, key, value); - }); - - fileKeys.forEach((key) => { - let file = d.get(state, `${key}File`); - if (file != undefined) { - if (renamedKeys[key]) { - key = renamedKeys[key]; - } - d.put(update, key, file); - } - }); - - return update; -} - -function getCurrentUrl() { - let [pre, _past] = window.location.pathname.split("/settings"); - return `${window.location.origin}${pre}/settings`; -} - -function fetchInstanceWithoutStore(domain) { - return function (dispatch, getState) { - return Promise.try(() => { - let lookup = getState().instances.info[domain]; - if (lookup != undefined) { - return lookup; - } - - // apiCall expects to pull the domain from state, - // but we don't want to store it there yet - // so we mock the API here with our function argument - let fakeState = { - oauth: { instance: domain } - }; - - return apiCall("GET", "/api/v1/instance")(dispatch, () => fakeState); - }).then((json) => { - if (json && json.uri) { // TODO: validate instance json more? - dispatch(setNamedInstanceInfo([domain, json])); - return json; - } - }); - }; -} - -function fetchInstance() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/instance")); - }).then((json) => { - if (json && json.uri) { - dispatch(setInstanceInfo(json)); - return json; - } - }); - }; -} - -let submoduleArgs = { apiCall, getCurrentUrl, getChanges }; - -module.exports = { - instance: { - fetchWithoutStore: fetchInstanceWithoutStore, - fetch: fetchInstance - }, - oauth: require("./oauth")(submoduleArgs), - user: require("./user")(submoduleArgs), - admin: require("./admin")(submoduleArgs), - apiCall, - convertToForm, - getChanges -};
\ No newline at end of file diff --git a/web/source/settings/lib/api/oauth.js b/web/source/settings/lib/api/oauth.js deleted file mode 100644 index 68095cac5..000000000 --- a/web/source/settings/lib/api/oauth.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); - -const { OAUTHError, AuthenticationError } = require("../errors"); - -const oauth = require("../../redux/reducers/oauth").actions; -const temporary = require("../../redux/reducers/temporary").actions; -const admin = require("../../redux/reducers/admin").actions; - -module.exports = function oauthAPI({ apiCall, getCurrentUrl }) { - return { - - register: function register(scopes = []) { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("POST", "/api/v1/apps", { - client_name: "GoToSocial Settings", - scopes: scopes.join(" "), - redirect_uris: getCurrentUrl(), - website: getCurrentUrl() - })); - }).then((json) => { - json.scopes = scopes; - dispatch(oauth.setRegistration(json)); - }); - }; - }, - - authorize: function authorize() { - return function (dispatch, getState) { - let state = getState(); - let reg = state.oauth.registration; - let base = new URL(state.oauth.instance); - - base.pathname = "/oauth/authorize"; - base.searchParams.set("client_id", reg.client_id); - base.searchParams.set("redirect_uri", getCurrentUrl()); - base.searchParams.set("response_type", "code"); - base.searchParams.set("scope", reg.scopes.join(" ")); - - dispatch(oauth.setLoginState("callback")); - dispatch(temporary.setStatus("Redirecting to instance login...")); - - // send user to instance's login flow - window.location.assign(base.href); - }; - }, - - tokenize: function tokenize(code) { - return function (dispatch, getState) { - let reg = getState().oauth.registration; - - return Promise.try(() => { - if (reg == undefined || reg.client_id == undefined) { - throw new OAUTHError("Callback code present, but no client registration is available from localStorage. \nNote: localStorage is unavailable in Private Browsing."); - } - - return dispatch(apiCall("POST", "/oauth/token", { - client_id: reg.client_id, - client_secret: reg.client_secret, - redirect_uri: getCurrentUrl(), - grant_type: "authorization_code", - code: code - })); - }).then((json) => { - window.history.replaceState({}, document.title, window.location.pathname); - return dispatch(oauth.login(json)); - }); - }; - }, - - checkIfAdmin: function checkIfAdmin() { - return function (dispatch, getState) { - const state = getState(); - let stored = state.oauth.isAdmin; - if (stored != undefined) { - return stored; - } - - // newer GoToSocial version will include a `role` in the Account data, check that first - if (state.user.profile.role == "admin") { - dispatch(oauth.setAdmin(true)); - return true; - } - - // no role info, try fetching an admin-only route and see if we get an error - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/admin/domain_blocks")); - }).then((data) => { - return Promise.all([ - dispatch(oauth.setAdmin(true)), - dispatch(admin.setBlockedInstances(data)) - ]); - }).catch(AuthenticationError, () => { - return dispatch(oauth.setAdmin(false)); - }); - }; - }, - - logout: function logout() { - return function (dispatch, _getState) { - // TODO: GoToSocial does not have a logout API route yet - - return dispatch(oauth.remove()); - }; - } - }; -};
\ No newline at end of file diff --git a/web/source/settings/lib/api/user.js b/web/source/settings/lib/api/user.js deleted file mode 100644 index 41031d489..000000000 --- a/web/source/settings/lib/api/user.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); - -const user = require("../../redux/reducers/user").actions; - -module.exports = function ({ apiCall, getChanges }) { - function updateCredentials(selector, keys) { - return function (dispatch, getState) { - return Promise.try(() => { - const state = selector(getState()); - - const update = getChanges(state, keys); - - return dispatch(apiCall("PATCH", "/api/v1/accounts/update_credentials", update, "form")); - }).then((account) => { - return dispatch(user.setAccount(account)); - }); - }; - } - - return { - fetchAccount: function fetchAccount() { - return function (dispatch, _getState) { - return Promise.try(() => { - return dispatch(apiCall("GET", "/api/v1/accounts/verify_credentials")); - }).then((account) => { - return dispatch(user.setAccount(account)); - }); - }; - }, - - updateProfile: function updateProfile() { - const formKeys = ["display_name", "locked", "source", "custom_css", "source.note", "enable_rss"]; - const renamedKeys = { - "source.note": "note" - }; - const fileKeys = ["header", "avatar"]; - - return updateCredentials((state) => state.user.profile, {formKeys, renamedKeys, fileKeys}); - }, - - updateSettings: function updateProfile() { - const formKeys = ["source"]; - - return updateCredentials((state) => state.user.settings, {formKeys}); - } - }; -};
\ No newline at end of file |