Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
// Push all the complex stages onto the pipeline as-is
while (!itr.done && itr.value.$isComplexStage) {
pipeline.push(...itr.value.pipeline);
if (itr.value.mutator) {
postAggregateMutation.push(itr.value.mutator);
}
itr = iterator.next();
}
}
if (args.search) {
// TODO: Implement configurable search fields for lists
pipeline.push({
$match: {
name: new RegExp(`${escapeRegExp(args.search)}`, 'i'),
},
});
}
if (args.orderBy) {
const [orderField, orderDirection] = args.orderBy.split('_');
pipeline.push({
$sort: {
[orderField]: orderDirection === 'ASC' ? 1 : -1,
},
});
}
if (args.skip < Infinity && args.skip > 0) {
pipeline.push({
deserializeItemData(item) {
return {
...mapKeys(this._fieldsByPath, field => field.deserialize(item)),
// Handle the special case of `_label_` (and potentially others)
...omit(item, Object.keys(this._fieldsByPath)),
};
}
(idFilters.id_in && idFilters.id_in.length === 0) ||
// All the passed in ids have been explicitly disallowed
(idFilters.id_not_in && idFilters.id_not_in.length === uniqueIds.length)
) {
// NOTE: We don't throw an error for multi-actions, only return an empty
// array because there's no mechanism in GraphQL to return more than one
// error for a list result.
return [];
}
// NOTE: The fields will be filtered by the ACL checking in gqlFieldResolvers()
// NOTE: Unlike in the single-operation variation, there is no security risk
// in returning the result of the query here, because if no items match, we
// return an empty array regardless of if that's because of lack of
// permissions or because of those items don't exist.
const remainingAccess = omit(access, ['id', 'id_not', 'id_in', 'id_not_in']);
return await this._itemsQuery(
{ where: { ...remainingAccess, ...idFilters } },
{ context, info }
);
}
function graphQlQueryToMongoJoinQuery(query) {
const _query = {
...query.where,
...mapKeyNames(
// Grab all the modifiers
pick(query, ['search', 'orderBy', 'skip', 'first']),
// and prefix with a dollar symbol so they can be picked out by the
// query builder tokeniser
key => `$${key}`
),
};
return mapKeys(_query, field => {
if (getType(field) !== 'Object' || !field.where) {
return field;
}
// recurse on object (ie; relationship) types
return graphQlQueryToMongoJoinQuery(field);
});
}
initFields() {
if (this.fieldsInitialised) return;
this.fieldsInitialised = true;
let sanitisedFieldsConfig = mapKeys(this._fields, (fieldConfig, path) => ({
...fieldConfig,
type: mapNativeTypeToKeystoneType(fieldConfig.type, this.key, path),
}));
// Add an 'id' field if none supplied
if (!sanitisedFieldsConfig.id) {
if (typeof this.adapter.parentAdapter.getDefaultPrimaryKeyConfig !== 'function') {
throw `No 'id' field given for the '${this.key}' list and the list adapter ` +
`in used (${this.adapter.key}) doesn't supply a default primary key config ` +
`(no 'getDefaultPrimaryKeyConfig()' function)`;
}
// Rebuild the object so id is "first"
sanitisedFieldsConfig = {
id: this.adapter.parentAdapter.getDefaultPrimaryKeyConfig(),
...sanitisedFieldsConfig,
};
getAdminMeta({ schemaName }) {
const schemaAccess = this.access[schemaName];
return {
key: this.key,
// Reduce to truthy values (functions can't be passed over the webpack
// boundary)
access: mapKeys(schemaAccess, val => !!val),
label: this.adminUILabels.label,
singular: this.adminUILabels.singular,
plural: this.adminUILabels.plural,
path: this.adminUILabels.path,
gqlNames: this.gqlNames,
fields: this.fields
.filter(field => field.access[schemaName].read)
.map(field => field.getAdminMeta({ schemaName })),
views: this.views,
adminConfig: {
defaultPageSize: this.adminConfig.defaultPageSize,
defaultColumns: this.adminConfig.defaultColumns.replace(/\s/g, ''), // remove all whitespace
defaultSort: this.adminConfig.defaultSort,
maximumPageSize: Math.max(
this.adminConfig.defaultPageSize,
this.adminConfig.maximumPageSize
onSave = async () => {
const { item, validationErrors, validationWarnings } = this.state;
// There are errors, no need to proceed - the entire save can be aborted.
if (countArrays(validationErrors)) {
return;
}
const {
onUpdate,
toastManager: { addToast },
updateItem,
item: initialData,
} = this.props;
const fieldsObject = this.getFieldsObject();
const initialValues = getInitialValues(fieldsObject, initialData);
const currentValues = getCurrentValues(fieldsObject, item);
// Don't try to update anything that hasn't changed.
const fields = Object.values(omitBy(fieldsObject, path => !data.hasOwnProperty(path)));
// On the first pass through, there wont be any warnings, so we go ahead
// and check.
// On the second pass through, there _may_ be warnings, and by this point
// we know there are no errors (see the `validationErrors` check above),
// if so, we let the user force the update through anyway and hence skip
// this check.
// Later, on every change, we reset the warnings, so we know if things
// have changed since last time we checked.
if (!countArrays(validationWarnings)) {
const { errors, warnings } = await validateFields(fields, item, data);
const totalErrors = countArrays(errors);
const totalWarnings = countArrays(warnings);
if (totalErrors + totalWarnings > 0) {
const messages = [];
if (totalErrors > 0) {
messages.push(`${totalErrors} error${totalErrors > 1 ? 's' : ''}`);
}
if (totalWarnings > 0) {
messages.push(`${totalWarnings} warning${totalWarnings > 1 ? 's' : ''}`);
}
addToast(`Validation failed: ${messages.join(' and ')}.`, {
autoDismiss: true,
appearance: errors.length ? 'error' : 'warning',
});
this.setState(() => ({
);
const fields = Object.values(omitBy(fieldsObject, path => !data.hasOwnProperty(path)));
// On the first pass through, there wont be any warnings, so we go ahead
// and check.
// On the second pass through, there _may_ be warnings, and by this point
// we know there are no errors (see the `validationErrors` check above),
// if so, we let the user force the update through anyway and hence skip
// this check.
// Later, on every change, we reset the warnings, so we know if things
// have changed since last time we checked.
if (!countArrays(validationWarnings)) {
const { errors, warnings } = await validateFields(fields, item, data);
const totalErrors = countArrays(errors);
const totalWarnings = countArrays(warnings);
if (totalErrors + totalWarnings > 0) {
const messages = [];
if (totalErrors > 0) {
messages.push(`${totalErrors} error${totalErrors > 1 ? 's' : ''}`);
}
if (totalWarnings > 0) {
messages.push(`${totalWarnings} warning${totalWarnings > 1 ? 's' : ''}`);
}
addToast(`Validation failed: ${messages.join(' and ')}.`, {
autoDismiss: true,
appearance: errors.length ? 'error' : 'warning',
});
constructor(config, adminMeta, views) {
this.config = config;
this.adminMeta = adminMeta;
// TODO: undo this
Object.assign(this, config);
this.fields = config.fields.map(fieldConfig => {
const [Controller] = adminMeta.readViews([views[fieldConfig.path].Controller]);
return new Controller(fieldConfig, this, adminMeta, views[fieldConfig.path]);
});
this._fieldsByPath = arrayToObject(this.fields, 'path');
this.createMutation = gql`
mutation create($data: ${this.gqlNames.createInputName}!) {
${this.gqlNames.createMutationName}(data: $data) {
id
_label_
}
}
`;
this.createManyMutation = gql`
mutation createMany($data: ${this.gqlNames.createManyInputName}!) {
${this.gqlNames.createManyMutationName}(data: $data) {
id
}
}
`;