import _ from 'lodash' export const getBooleanValue = value => { if (value === 'true') { return true } else if (value === 'false') { return false } return value } const getCurrentValue = (type, value, path) => { if (type === 'state') { return _.get(value, path) } else { const [firstSettingName, ...restKeys] = path const firstSegment = value[firstSettingName] if (restKeys.length === 0 || !firstSegment) { return firstSegment || false } else { const secondSegment = (value, keys) => { const [element, ...rest] = keys return keys.length === 0 ? value : secondSegment(value[1][element], rest) } return secondSegment(firstSegment, restKeys) } } } const getValueWithoutKey = (key, [type, value]) => { if (prependWithColon(type, value)) { return `:${value}` } else if (key === ':backends') { const index = value.findIndex(el => el === ':ex_syslogger') const updatedArray = value.slice() if (index !== -1) { updatedArray[index] = { 'tuple': ['ExSyslogger', ':ex_syslogger'] } } return updatedArray } else if (key === ':types') { return Object.keys(value).reduce((acc, key) => { return { ...acc, [key]: value[key][1] } }, {}) } return value } export const parseNonTuples = (key, value) => { if (key === ':backends') { const index = value.findIndex(el => typeof el === 'object' && el.tuple.includes(':ex_syslogger')) const updated = value.map((el, i) => i === index ? ':ex_syslogger' : el) return updated } if (key === ':args') { return typeof value === 'string' ? [value] : value } return value } // REFACTOR export const parseTuples = (tuples, key) => { return tuples.reduce((accum, item) => { if (key === ':rate_limit' || (key === 'Pleroma.Web.Endpoint.MetricsExporter' && item.tuple[0] === ':auth')) { const getValue = () => { if (typeof item.tuple[1] === 'boolean') { return item.tuple[1] } else if (Array.isArray(item.tuple[1])) { return item.tuple[1].map(el => el.tuple) } else { return item.tuple[1].tuple } } accum[item.tuple[0]] = getValue() } else if (item.tuple[0] === ':mascots') { accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => { return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] }, []) } else if (Array.isArray(item.tuple[1]) && (item.tuple[0] === ':groups' || item.tuple[0] === ':replace' || item.tuple[0] === ':retries' || (item.tuple[0] === ':headers' && key === 'Pleroma.Web.MediaProxy.Invalidation.Http') || item.tuple[0] === ':crontab' || item.tuple[0] === ':transparency_exclusions' || item.tuple[0] === ':quarantined_instances' || key === ':mrf_simple')) { if (item.tuple[0] === ':crontab') { accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => { return [...acc, { [group.tuple[1]]: { value: group.tuple[0], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] }, []) } else { accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => { /** * The ':quarantined_instances' and ':mrf_simple' settings have changed to a list of tuples instead of a list of strings. * This is to have backwards compatibility for instances that still use strings. */ if (typeof group === 'string') { return [...acc, group] } else { return [...acc, { [group.tuple[0]]: { value: group.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] } }, []) } } else if (item.tuple[0] === ':icons') { accum[item.tuple[0]] = item.tuple[1].map(icon => { return Object.keys(icon).map(name => { return { key: name, value: icon[name], id: `f${(~~(Math.random() * 1e8)).toString(16)}` } }) }, []) } else if (item.tuple[0] === ':prune') { accum[item.tuple[0]] = item.tuple[1] === ':disabled' ? [item.tuple[1]] : item.tuple[1].tuple } else if (item.tuple[0] === ':sender') { accum[item.tuple[0]] = parseStringOrTupleValue(item.tuple[0], item.tuple[1]) } else if (item.tuple[0] === ':args') { accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1]) } else if (item.tuple[0] === ':ip_whitelist') { accum[item.tuple[0]] = item.tuple[1].map(ip => typeof ip === 'string' ? ip : ip.tuple.join('.')) } else if (Array.isArray(item.tuple[1]) && (item.tuple[1][0] !== null && typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) { accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0]) } else if (Array.isArray(item.tuple[1])) { accum[item.tuple[0]] = item.tuple[1] } else if (item.tuple[0] === ':ip') { accum[item.tuple[0]] = item.tuple[1].tuple.join('.') } else if (item.tuple[1] && typeof item.tuple[1] === 'object') { if (item.tuple[0] === ':params' || item.tuple[0] === ':match_actor') { accum[item.tuple[0]] = Object.keys(item.tuple[1]).reduce((acc, key) => { return [...acc, { [key]: { value: item.tuple[1][key], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] }, []) } else { accum[item.tuple[0]] = parseObject(item.tuple[1]) } } else { accum[item.tuple[0]] = item.tuple[1] } return accum }, {}) } const parseObject = object => { return Object.keys(object).reduce((acc, item) => { acc[item] = object[item] return acc }, {}) } const parseStringOrTupleValue = (key, value) => { if (key === ':proxy_url') { if (value && !Array.isArray(value) && typeof value === 'object' && value.tuple.length === 3 && value.tuple[0] === ':socks5') { const [, host, port] = value.tuple return { socks5: true, host, port } } else if (typeof value === 'string') { const [host, port] = value.split(':') return { socks5: false, host, port } } return { socks5: false, host: null, port: null } } else if (key === ':sender') { if (typeof value === 'string') { return { email: value } } else if (value && typeof value === 'object' && value.tuple.length === 2) { const [nickname, email] = value.tuple return { nickname, email } } } } const prependWithColon = (type, value) => { return (type === 'atom' && value.length > 0) || (Array.isArray(type) && type.includes('boolean') && type.includes('atom') && typeof value === 'string') } export const processNested = (valueForState, valueForUpdatedSettings, group, parentKey, parents, settings, updatedSettings) => { const [{ key, type }, ...otherParents] = parents const path = [group, parentKey, ...parents.reverse().map(parent => parent.key).slice(0, -1)] let updatedValueForState = valueExists('state', settings, path) ? { ...getCurrentValue('state', settings[group][parentKey], parents.map(el => el.key).slice(0, -1)), ...{ [key]: valueForState }} : { [key]: valueForState } let updatedValueForUpdatedSettings = valueExists('updatedSettings', updatedSettings, path) ? { ...getCurrentValue('updatedSettings', updatedSettings[group][parentKey], parents.map(el => el.key).slice(0, -1))[1], ...{ [key]: [type, valueForUpdatedSettings] }} : { [key]: [type, valueForUpdatedSettings] } if (group === ':mime' && parents[0].key === ':types') { updatedValueForState = settings[group][parents[0].key] ? { ...settings[group][parents[0].key].value, ...updatedValueForState } : updatedValueForState updatedValueForUpdatedSettings = settings[group][parents[0].key] ? { ...Object.keys(settings[group][parents[0].key].value) .reduce((acc, el) => { return { ...acc, [el]: [type, settings[group][parents[0].key].value[el]] } }, {}), ...updatedValueForUpdatedSettings } : updatedValueForUpdatedSettings } return otherParents.length === 1 ? { valueForState: updatedValueForState, valueForUpdatedSettings: updatedValueForUpdatedSettings, setting: otherParents[0] } : processNested(updatedValueForState, updatedValueForUpdatedSettings, group, parentKey, otherParents, settings, updatedSettings) } const valueExists = (type, value, path) => { if (type === 'state') { return _.get(value, path) } else { const [group, key, firstSettingName, ...restKeys] = path const firstSegment = _.get(value, [group, key, firstSettingName]) if (restKeys.length === 0 || !firstSegment) { return firstSegment || false } else { const secondSegment = (value, keys) => { if (keys.length === 0) { return true } const [element, ...rest] = keys return value[1][element] ? secondSegment(value[1][element], rest) : false } return secondSegment(firstSegment, restKeys) } } } export const valueHasTuples = (key, value) => { const valueIsArrayOfNonObjects = Array.isArray(value) && value.length > 0 && value.every(el => typeof el !== 'object') return key === ':meta' || key === ':types' || key === ':backends' || key === ':compiled_template_engines' || key === ':compiled_format_encoders' || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null || valueIsArrayOfNonObjects } export const wrapUpdatedSettings = (group, settings, currentState) => { return Object.keys(settings).map((key) => { return settings[key]._value ? { group, key, value: getValueWithoutKey(key, settings[key]._value) } : { group, key, value: wrapValues(settings[key], currentState[group][key]) } }) } const wrapValues = (settings, currentState) => { return Object.keys(settings).map(setting => { const [type, value] = settings[setting] if (type === 'keyword' || (Array.isArray(type) && ( type.includes('keyword') || (type.includes('tuple') && type.includes('list')) )) ) { return { 'tuple': [setting, wrapValues(value, currentState)] } } else if (prependWithColon(type, value)) { return { 'tuple': [setting, `:${value}`] } } else if (type.includes('tuple') && (type.includes('string') || type.includes('atom') || type.includes('boolean'))) { return typeof value === 'string' || typeof value === 'boolean' ? { 'tuple': [setting, value] } : { 'tuple': [setting, { 'tuple': value }] } } else if (type === 'reversed_tuple') { return { 'tuple': [value, setting] } } else if (type === 'map') { const mapValue = Object.keys(value).reduce((acc, key) => { acc[key] = value[key][1] return acc }, {}) return { 'tuple': [setting, { ...currentState[setting], ...mapValue }] } } else if (type.includes('map') && !type.includes('list')) { const mapValue = Object.keys(value).reduce((acc, key) => { acc[key] = value[key][1] return acc }, {}) return { 'tuple': [setting, mapValue] } } else if (setting === ':ip') { const ip = value.split('.').map(s => parseInt(s, 10)) return { 'tuple': [setting, { 'tuple': ip }] } } else { return { 'tuple': [setting, value] } } }) } export const formSearchObject = description => { const parseNestedSettings = (description, label, key) => description.reduce((acc, setting) => { const searchArray = _.compact([setting.key, setting.label, setting.description]).map(el => el.toLowerCase()) const resultObject = { label: setting.label, key: setting.key || setting.group, groupKey: key, groupLabel: label, search: searchArray } if (setting.children) { const updatedAcc = [...acc, resultObject] return [...updatedAcc, ...parseNestedSettings(setting.children, label, key)] } return [...acc, resultObject] }, []) const processedDescription = description.reduce((acc, setting) => { const searchArray = _.compact([setting.key, setting.label, setting.description]).map(el => el.toLowerCase()) const resultObject = { label: setting.label, key: setting.key || setting.group, groupKey: setting.key || setting.group, groupLabel: setting.label, search: searchArray } if (setting.children) { const updatedAcc = !setting.key && setting.group === ':pleroma' ? acc : [...acc, resultObject] return [...updatedAcc, ...parseNestedSettings(setting.children, setting.label, setting.key || setting.group)] } return !setting.key && setting.group === ':pleroma' ? acc : [...acc, resultObject] }, []) const searchDataForEditableDocs = [{ groupKey: ':instance_panel', groupLabel: 'Instance Panel', key: ':instance_panel', label: 'Instance Panel', search: ['Instance Panel', ':instance_panel'] }, { groupKey: ':terms_of_services', groupLabel: 'Terms of Services', key: ':terms_of_services', label: 'Terms of Services', search: ['Terms of Services', ':terms_of_services'] }] return processedDescription.concat(searchDataForEditableDocs) }