Allow to unset file from graphql (#6651)

This commit is contained in:
Antoine Cormouls
2020-04-30 07:18:25 +02:00
committed by GitHub
parent d279198db7
commit 4d1bd9346f
3 changed files with 85 additions and 50 deletions

View File

@@ -9522,6 +9522,29 @@ describe('ParseGraphQLServer', () => {
expect(res.status).toEqual(200);
expect(await res.text()).toEqual('My File Content');
const mutationResult = await apolloClient.mutate({
mutation: gql`
mutation UnlinkFile($id: ID!) {
updateSomeClass(
input: { id: $id, fields: { someField: { file: null } } }
) {
someClass {
someField {
name
url
}
}
}
}
`,
variables: {
id: result2.data.createSomeClass3.someClass.id,
},
});
expect(
mutationResult.data.updateSomeClass.someClass.someField
).toEqual(null);
} catch (e) {
handleError(e);
}

View File

@@ -23,7 +23,7 @@ class TypeValidationError extends Error {
}
}
const parseStringValue = value => {
const parseStringValue = (value) => {
if (typeof value === 'string') {
return value;
}
@@ -31,7 +31,7 @@ const parseStringValue = value => {
throw new TypeValidationError(value, 'String');
};
const parseIntValue = value => {
const parseIntValue = (value) => {
if (typeof value === 'string') {
const int = Number(value);
if (Number.isInteger(int)) {
@@ -42,7 +42,7 @@ const parseIntValue = value => {
throw new TypeValidationError(value, 'Int');
};
const parseFloatValue = value => {
const parseFloatValue = (value) => {
if (typeof value === 'string') {
const float = Number(value);
if (!isNaN(float)) {
@@ -53,7 +53,7 @@ const parseFloatValue = value => {
throw new TypeValidationError(value, 'Float');
};
const parseBooleanValue = value => {
const parseBooleanValue = (value) => {
if (typeof value === 'boolean') {
return value;
}
@@ -61,7 +61,7 @@ const parseBooleanValue = value => {
throw new TypeValidationError(value, 'Boolean');
};
const parseValue = value => {
const parseValue = (value) => {
switch (value.kind) {
case Kind.STRING:
return parseStringValue(value.value);
@@ -86,15 +86,15 @@ const parseValue = value => {
}
};
const parseListValues = values => {
const parseListValues = (values) => {
if (Array.isArray(values)) {
return values.map(value => parseValue(value));
return values.map((value) => parseValue(value));
}
throw new TypeValidationError(values, 'List');
};
const parseObjectFields = fields => {
const parseObjectFields = (fields) => {
if (Array.isArray(fields)) {
return fields.reduce(
(object, field) => ({
@@ -112,9 +112,9 @@ const ANY = new GraphQLScalarType({
name: 'Any',
description:
'The Any scalar type is used in operations and types that involve any type of value.',
parseValue: value => value,
serialize: value => value,
parseLiteral: ast => parseValue(ast),
parseValue: (value) => value,
serialize: (value) => value,
parseLiteral: (ast) => parseValue(ast),
});
const OBJECT = new GraphQLScalarType({
@@ -144,7 +144,7 @@ const OBJECT = new GraphQLScalarType({
},
});
const parseDateIsoValue = value => {
const parseDateIsoValue = (value) => {
if (typeof value === 'string') {
const date = new Date(value);
if (!isNaN(date)) {
@@ -157,7 +157,7 @@ const parseDateIsoValue = value => {
throw new TypeValidationError(value, 'Date');
};
const serializeDateIso = value => {
const serializeDateIso = (value) => {
if (typeof value === 'string') {
return value;
}
@@ -168,7 +168,7 @@ const serializeDateIso = value => {
throw new TypeValidationError(value, 'Date');
};
const parseDateIsoLiteral = ast => {
const parseDateIsoLiteral = (ast) => {
if (ast.kind === Kind.STRING) {
return parseDateIsoValue(ast.value);
}
@@ -219,8 +219,8 @@ const DATE = new GraphQLScalarType({
iso: parseDateIsoLiteral(ast),
};
} else if (ast.kind === Kind.OBJECT) {
const __type = ast.fields.find(field => field.name.value === '__type');
const iso = ast.fields.find(field => field.name.value === 'iso');
const __type = ast.fields.find((field) => field.name.value === '__type');
const iso = ast.fields.find((field) => field.name.value === 'iso');
if (__type && __type.value && __type.value.value === 'Date' && iso) {
return {
__type: __type.value.value,
@@ -273,8 +273,8 @@ const BYTES = new GraphQLScalarType({
base64: ast.value,
};
} else if (ast.kind === Kind.OBJECT) {
const __type = ast.fields.find(field => field.name.value === '__type');
const base64 = ast.fields.find(field => field.name.value === 'base64');
const __type = ast.fields.find((field) => field.name.value === '__type');
const base64 = ast.fields.find((field) => field.name.value === 'base64');
if (
__type &&
__type.value &&
@@ -294,7 +294,7 @@ const BYTES = new GraphQLScalarType({
},
});
const parseFileValue = value => {
const parseFileValue = (value) => {
if (typeof value === 'string') {
return {
__type: 'File',
@@ -317,7 +317,7 @@ const FILE = new GraphQLScalarType({
description:
'The File scalar type is used in operations and types that involve files.',
parseValue: parseFileValue,
serialize: value => {
serialize: (value) => {
if (typeof value === 'string') {
return value;
} else if (
@@ -335,9 +335,9 @@ const FILE = new GraphQLScalarType({
if (ast.kind === Kind.STRING) {
return parseFileValue(ast.value);
} else if (ast.kind === Kind.OBJECT) {
const __type = ast.fields.find(field => field.name.value === '__type');
const name = ast.fields.find(field => field.name.value === 'name');
const url = ast.fields.find(field => field.name.value === 'url');
const __type = ast.fields.find((field) => field.name.value === '__type');
const name = ast.fields.find((field) => field.name.value === 'name');
const url = ast.fields.find((field) => field.name.value === 'url');
if (__type && __type.value && name && name.value) {
return parseFileValue({
__type: __type.value.value,
@@ -371,13 +371,19 @@ const FILE_INPUT = new GraphQLInputObjectType({
name: 'FileInput',
fields: {
file: {
description: 'A File Scalar can be an url or a FileInfo object.',
description:
'A File Scalar can be an url or a FileInfo object. If this field is set to null the file will be unlinked.',
type: FILE,
},
upload: {
description: 'Use this field if you want to create a new file.',
type: GraphQLUpload,
},
unlink: {
description:
'Use this field if you want to unlink the file (the file will not be deleted on cloud storage)',
type: GraphQLBoolean,
},
},
});
@@ -551,7 +557,7 @@ const ACL = new GraphQLObjectType({
type: new GraphQLList(new GraphQLNonNull(USER_ACL)),
resolve(p) {
const users = [];
Object.keys(p).forEach(rule => {
Object.keys(p).forEach((rule) => {
if (rule !== '*' && rule.indexOf('role:') !== 0) {
users.push({
userId: toGlobalId('_User', rule),
@@ -568,7 +574,7 @@ const ACL = new GraphQLObjectType({
type: new GraphQLList(new GraphQLNonNull(ROLE_ACL)),
resolve(p) {
const roles = [];
Object.keys(p).forEach(rule => {
Object.keys(p).forEach((rule) => {
if (rule.indexOf('role:') === 0) {
roles.push({
roleName: rule.replace('role:', ''),
@@ -839,49 +845,49 @@ const GEO_INTERSECTS_INPUT = new GraphQLInputObjectType({
},
});
const equalTo = type => ({
const equalTo = (type) => ({
description:
'This is the equalTo operator to specify a constraint to select the objects where the value of a field equals to a specified value.',
type,
});
const notEqualTo = type => ({
const notEqualTo = (type) => ({
description:
'This is the notEqualTo operator to specify a constraint to select the objects where the value of a field do not equal to a specified value.',
type,
});
const lessThan = type => ({
const lessThan = (type) => ({
description:
'This is the lessThan operator to specify a constraint to select the objects where the value of a field is less than a specified value.',
type,
});
const lessThanOrEqualTo = type => ({
const lessThanOrEqualTo = (type) => ({
description:
'This is the lessThanOrEqualTo operator to specify a constraint to select the objects where the value of a field is less than or equal to a specified value.',
type,
});
const greaterThan = type => ({
const greaterThan = (type) => ({
description:
'This is the greaterThan operator to specify a constraint to select the objects where the value of a field is greater than a specified value.',
type,
});
const greaterThanOrEqualTo = type => ({
const greaterThanOrEqualTo = (type) => ({
description:
'This is the greaterThanOrEqualTo operator to specify a constraint to select the objects where the value of a field is greater than or equal to a specified value.',
type,
});
const inOp = type => ({
const inOp = (type) => ({
description:
'This is the in operator to specify a constraint to select the objects where the value of a field equals any value in the specified array.',
type: new GraphQLList(type),
});
const notIn = type => ({
const notIn = (type) => ({
description:
'This is the notIn operator to specify a constraint to select the objects where the value of a field do not equal any value in the specified array.',
type: new GraphQLList(type),
@@ -1219,14 +1225,14 @@ let ARRAY_RESULT;
const loadArrayResult = (parseGraphQLSchema, parseClasses) => {
const classTypes = parseClasses
.filter(parseClass =>
.filter((parseClass) =>
parseGraphQLSchema.parseClassTypes[parseClass.className]
.classGraphQLOutputType
? true
: false
)
.map(
parseClass =>
(parseClass) =>
parseGraphQLSchema.parseClassTypes[parseClass.className]
.classGraphQLOutputType
);
@@ -1235,7 +1241,7 @@ const loadArrayResult = (parseGraphQLSchema, parseClasses) => {
description:
'Use Inline Fragment on Array to get results: https://graphql.org/learn/queries/#inline-fragments',
types: () => [ELEMENT, ...classTypes],
resolveType: value => {
resolveType: (value) => {
if (value.__type === 'Object' && value.className && value.objectId) {
if (parseGraphQLSchema.parseClassTypes[value.className]) {
return parseGraphQLSchema.parseClassTypes[value.className]
@@ -1251,7 +1257,7 @@ const loadArrayResult = (parseGraphQLSchema, parseClasses) => {
parseGraphQLSchema.graphQLTypes.push(ARRAY_RESULT);
};
const load = parseGraphQLSchema => {
const load = (parseGraphQLSchema) => {
parseGraphQLSchema.addGraphQLType(GraphQLUpload, true);
parseGraphQLSchema.addGraphQLType(ANY, true);
parseGraphQLSchema.addGraphQLType(OBJECT, true);

View File

@@ -15,7 +15,7 @@ const transformTypes = async (
config: { isCreateEnabled, isUpdateEnabled },
} = parseGraphQLSchema.parseClassTypes[className];
const parseClass = parseGraphQLSchema.parseClasses.find(
clazz => clazz.className === className
(clazz) => clazz.className === className
);
if (fields) {
const classGraphQLCreateTypeFields =
@@ -26,7 +26,7 @@ const transformTypes = async (
isUpdateEnabled && classGraphQLUpdateType
? classGraphQLUpdateType.getFields()
: null;
const promises = Object.keys(fields).map(async field => {
const promises = Object.keys(fields).map(async (field) => {
let inputTypeField;
if (inputType === 'create' && classGraphQLCreateTypeFields) {
inputTypeField = classGraphQLCreateTypeFields[field];
@@ -73,6 +73,9 @@ const transformTypes = async (
const transformers = {
file: async ({ file, upload }, { config }) => {
if (file === null && !upload) {
return null;
}
if (upload) {
const { fileInfo } = await handleUpload(upload, config);
return { ...fileInfo, __type: 'File' };
@@ -81,15 +84,18 @@ const transformers = {
}
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.');
},
polygon: value => ({
polygon: (value) => ({
__type: 'Polygon',
coordinates: value.map(geoPoint => [geoPoint.latitude, geoPoint.longitude]),
coordinates: value.map((geoPoint) => [
geoPoint.latitude,
geoPoint.longitude,
]),
}),
geoPoint: value => ({
geoPoint: (value) => ({
...value,
__type: 'GeoPoint',
}),
ACL: value => {
ACL: (value) => {
const parseACL = {};
if (value.public) {
parseACL['*'] = {
@@ -98,7 +104,7 @@ const transformers = {
};
}
if (value.users) {
value.users.forEach(rule => {
value.users.forEach((rule) => {
const globalIdObject = fromGlobalId(rule.userId);
if (globalIdObject.type === '_User') {
rule.userId = globalIdObject.id;
@@ -110,7 +116,7 @@ const transformers = {
});
}
if (value.roles) {
value.roles.forEach(rule => {
value.roles.forEach((rule) => {
parseACL[`role:${rule.roleName}`] = {
read: rule.read,
write: rule.write,
@@ -141,7 +147,7 @@ const transformers = {
if (value.createAndAdd) {
nestedObjectsToAdd = (
await Promise.all(
value.createAndAdd.map(async input => {
value.createAndAdd.map(async (input) => {
const parseFields = await transformTypes('create', input, {
className: targetClass,
parseGraphQLSchema,
@@ -156,7 +162,7 @@ const transformers = {
);
})
)
).map(object => ({
).map((object) => ({
__type: 'Pointer',
className: targetClass,
objectId: object.objectId,
@@ -165,7 +171,7 @@ const transformers = {
if (value.add || nestedObjectsToAdd.length > 0) {
if (!value.add) value.add = [];
value.add = value.add.map(input => {
value.add = value.add.map((input) => {
const globalIdObject = fromGlobalId(input);
if (globalIdObject.type === targetClass) {
input = globalIdObject.id;
@@ -185,7 +191,7 @@ const transformers = {
if (value.remove) {
op.ops.push({
__op: 'RemoveRelation',
objects: value.remove.map(input => {
objects: value.remove.map((input) => {
const globalIdObject = fromGlobalId(input);
if (globalIdObject.type === targetClass) {
input = globalIdObject.id;