Allow to unset file from graphql (#6651)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user