mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
rework form parsing to properly handle async values
This commit is contained in:
parent
82aab9a984
commit
76c6a0eb70
4 changed files with 72 additions and 37 deletions
|
@ -375,19 +375,64 @@ export function formatYunoHostConfigPanels (data) {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format helper for a form value.
|
* Parse a front-end value to its API equivalent. This function returns a Promise or an
|
||||||
* Convert Boolean to (1|0) and concatenate adresses.
|
* Object `{ key: Promise }` if `key` is supplied. When parsing a form, all those
|
||||||
|
* objects must be merged to define the final sent form.
|
||||||
|
*
|
||||||
|
* Convert Boolean to '1' (true) or '0' (false),
|
||||||
|
* Concatenate two parts adresses (subdomain or email for example) into a single string,
|
||||||
|
* Convert File to its Base64 representation or set its value to '' to ask for a removal.
|
||||||
*
|
*
|
||||||
* @param {*} value
|
* @param {*} value
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
export function formatFormDataValue (value) {
|
export function formatFormDataValue (value, key = null) {
|
||||||
if (typeof value === 'boolean') {
|
if (Array.isArray(value)) {
|
||||||
return value ? 1 : 0
|
return Promise.all(
|
||||||
} else if (isObjectLiteral(value) && 'separator' in value) {
|
value.map(value_ => formatFormDataValue(value_))
|
||||||
return Object.values(value).join('')
|
).then(resolvedValues => ({ [key]: resolvedValues }))
|
||||||
}
|
}
|
||||||
return value
|
|
||||||
|
let result = value
|
||||||
|
if (typeof value === 'boolean') result = value ? 1 : 0
|
||||||
|
if (isObjectLiteral(value) && 'file' in value) {
|
||||||
|
// File has to be deleted
|
||||||
|
if (value.removed) result = ''
|
||||||
|
// File has not changed (will not be sent)
|
||||||
|
else if (value.current_file || value.file === null) result = null
|
||||||
|
else {
|
||||||
|
return getFileContent(value.file, { base64: true }).then(content => {
|
||||||
|
return {
|
||||||
|
[key]: content.replace(/data:[^;]*;base64,/, ''),
|
||||||
|
[key + '[name]']: value.file.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (isObjectLiteral(value) && 'separator' in value) {
|
||||||
|
result = Object.values(value).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a resolved Promise for non async values
|
||||||
|
return Promise.resolve(key ? { [key]: result } : result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convinient helper to properly parse a front-end form to its API equivalent.
|
||||||
|
* This parse each values asynchronously, allow to inject keys into the final form and
|
||||||
|
* make sure every async values resolves before resolving itself.
|
||||||
|
*
|
||||||
|
* @param {Object} formData
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function formatFormDataValues (formData) {
|
||||||
|
const promisedValues = Object.entries(formData).map(([key, value]) => {
|
||||||
|
return formatFormDataValue(value, key)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(promisedValues).then(resolvedValues => {
|
||||||
|
return resolvedValues.reduce((form, obj) => ({ ...form, ...obj }), {})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,38 +448,28 @@ export function formatFormDataValue (value) {
|
||||||
*/
|
*/
|
||||||
export async function formatFormData (
|
export async function formatFormData (
|
||||||
formData,
|
formData,
|
||||||
{ extract = null, flatten = false, removeEmpty = true, removeNull = false, multipart = true } = {}
|
{ extract = null, flatten = false, removeEmpty = true, removeNull = false } = {}
|
||||||
) {
|
) {
|
||||||
const output = {
|
const output = {
|
||||||
data: {},
|
data: {},
|
||||||
extracted: {}
|
extracted: {}
|
||||||
}
|
}
|
||||||
const promises = []
|
|
||||||
for (const key in formData) {
|
|
||||||
const type = extract && extract.includes(key) ? 'extracted' : 'data'
|
|
||||||
const value = Array.isArray(formData[key])
|
|
||||||
? formData[key].map(item => formatFormDataValue(item))
|
|
||||||
: formatFormDataValue(formData[key])
|
|
||||||
|
|
||||||
|
const values = await formatFormDataValues(formData)
|
||||||
|
for (const key in values) {
|
||||||
|
const type = extract && extract.includes(key) ? 'extracted' : 'data'
|
||||||
|
const value = values[key]
|
||||||
if (removeEmpty && isEmptyValue(value)) {
|
if (removeEmpty && isEmptyValue(value)) {
|
||||||
continue
|
continue
|
||||||
} else if (removeNull && (value === null || value === undefined)) {
|
} else if (removeNull && [null, undefined].includes(value)) {
|
||||||
continue
|
continue
|
||||||
} else if (value instanceof File && !multipart) {
|
|
||||||
if (value.currentfile) {
|
|
||||||
continue
|
|
||||||
} else if (value._removed) {
|
|
||||||
output[type][key] = ''
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
promises.push(pFileReader(value, output[type], key))
|
|
||||||
} else if (flatten && isObjectLiteral(value)) {
|
} else if (flatten && isObjectLiteral(value)) {
|
||||||
flattenObjectLiteral(value, output[type])
|
flattenObjectLiteral(value, output[type])
|
||||||
} else {
|
} else {
|
||||||
output[type][key] = value
|
output[type][key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (promises.length) await Promise.all(promises)
|
|
||||||
const { data, extracted } = output
|
const { data, extracted } = output
|
||||||
return extract ? { data, ...extracted } : data
|
return extract ? { data, ...extracted } : data
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,16 +50,16 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async applyConfig (id_) {
|
async applyConfig (id_) {
|
||||||
const formatedData = await formatFormData(
|
const args = await formatFormData(
|
||||||
this.config.forms[id_],
|
this.config.forms[id_],
|
||||||
{ removeEmpty: false, removeNull: true, multipart: false }
|
{ removeEmpty: false, removeNull: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
api.put(
|
api.put(
|
||||||
`apps/${this.id}/config`,
|
`apps/${this.id}/config`,
|
||||||
{ key: id_, args: objectToParams(formatedData) },
|
{ key: id_, args: objectToParams(args) },
|
||||||
{ key: 'apps.update_config', name: this.id }
|
{ key: 'apps.update_config', name: this.id }
|
||||||
).then(response => {
|
).then(() => {
|
||||||
this.$refs.view.fetchQueries({ triggerLoading: true })
|
this.$refs.view.fetchQueries({ triggerLoading: true })
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (err.name !== 'APIBadRequestError') throw err
|
if (err.name !== 'APIBadRequestError') throw err
|
||||||
|
|
|
@ -124,7 +124,7 @@ export default {
|
||||||
|
|
||||||
const { data: args, label } = await formatFormData(
|
const { data: args, label } = await formatFormData(
|
||||||
this.form,
|
this.form,
|
||||||
{ extract: ['label'], removeEmpty: false, removeNull: true, multipart: false }
|
{ extract: ['label'], removeEmpty: false, removeNull: true }
|
||||||
)
|
)
|
||||||
const data = { app: this.id, label, args: Object.entries(args).length ? objectToParams(args) : undefined }
|
const data = { app: this.id, label, args: Object.entries(args).length ? objectToParams(args) : undefined }
|
||||||
|
|
||||||
|
|
|
@ -41,23 +41,23 @@ export default {
|
||||||
this.config = formatYunoHostConfigPanels(config)
|
this.config = formatYunoHostConfigPanels(config)
|
||||||
},
|
},
|
||||||
|
|
||||||
async applyConfig (id_) {
|
async applyConfig (id) {
|
||||||
const formatedData = await formatFormData(
|
const args = await formatFormData(
|
||||||
this.config.forms[id_],
|
this.config.forms[id],
|
||||||
{ removeEmpty: false, removeNull: true, multipart: false }
|
{ removeEmpty: false, removeNull: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
api.put(
|
api.put(
|
||||||
`domains/${this.name}/config`,
|
`domains/${this.name}/config`,
|
||||||
{ key: id_, args: objectToParams(formatedData) },
|
{ key: id, args: objectToParams(args) },
|
||||||
{ key: 'domains.update_config', name: this.name }
|
{ key: 'domains.update_config', name: this.name }
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.$refs.view.fetchQueries({ triggerLoading: true })
|
this.$refs.view.fetchQueries({ triggerLoading: true })
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (err.name !== 'APIBadRequestError') throw err
|
if (err.name !== 'APIBadRequestError') throw err
|
||||||
const panel = this.config.panels.find(({ id }) => id_ === id)
|
const panel = this.config.panels.find(panel => panel.id === id)
|
||||||
if (err.data.name) {
|
if (err.data.name) {
|
||||||
this.config.errors[id_][err.data.name].message = err.message
|
this.config.errors[id][err.data.name].message = err.message
|
||||||
} else this.$set(panel, 'serverError', err.message)
|
} else this.$set(panel, 'serverError', err.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue