diff --git a/backend/package.json b/backend/package.json index 8f1bc2de2..2457b9dee 100644 --- a/backend/package.json +++ b/backend/package.json @@ -48,7 +48,6 @@ "express": "~4.16.4", "faker": "~4.1.0", "graphql": "~14.2.1", - "graphql-constraint-directive": "^1.3.0", "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.2", diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 8f86a88e6..3073264ba 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -10,6 +10,7 @@ import permissionsMiddleware from './permissionsMiddleware' import userMiddleware from './userMiddleware' import includedFieldsMiddleware from './includedFieldsMiddleware' import orderByMiddleware from './orderByMiddleware' +import validUrlMiddleware from './validUrlMiddleware' export default schema => { let middleware = [ @@ -22,7 +23,8 @@ export default schema => { softDeleteMiddleware, userMiddleware, includedFieldsMiddleware, - orderByMiddleware + orderByMiddleware, + validUrlMiddleware ] // add permisions middleware at the first position (unless we're seeding) diff --git a/backend/src/middleware/validUrlMiddleware.js b/backend/src/middleware/validUrlMiddleware.js new file mode 100644 index 000000000..f5fdae9d4 --- /dev/null +++ b/backend/src/middleware/validUrlMiddleware.js @@ -0,0 +1,18 @@ +const validURL = str => { + const isValid = str.match(/^(?:https?:\/\/)(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g) + return !!isValid +} + +export default { + Mutation: { + CreateSocialMedia: async (resolve, root, args, context, info) => { + let socialMedia + if (validURL(args.url)) { + socialMedia = await resolve(root, args, context, info) + } else { + throw Error('Input is not a URL') + } + return socialMedia + } + } +} \ No newline at end of file diff --git a/backend/src/resolvers/socialMedia.js b/backend/src/resolvers/socialMedia.js index f201f4365..a24cc6802 100644 --- a/backend/src/resolvers/socialMedia.js +++ b/backend/src/resolvers/socialMedia.js @@ -4,12 +4,11 @@ export default { Mutation: { CreateSocialMedia: async (object, params, context, resolveInfo) => { const result = await neo4jgraphql(object, params, context, resolveInfo, true) - const session = context.driver.session() await session.run( - 'MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId}) ' + - 'MERGE (socialMedia)<-[:OWNED]-(owner) ' + - 'RETURN owner', { + `MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId}) + MERGE (socialMedia)<-[:OWNED]-(owner) + RETURN owner`, { userId: context.user.id, socialMediaId: result.id } diff --git a/backend/src/resolvers/socialMedia.spec.js b/backend/src/resolvers/socialMedia.spec.js new file mode 100644 index 000000000..92a08823e --- /dev/null +++ b/backend/src/resolvers/socialMedia.spec.js @@ -0,0 +1,49 @@ +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() + +describe('CreateSocialMedia', () => { + let client + let headers + const mutation = ` + mutation($url: String!) { + CreateSocialMedia(url: $url) { + url + } + } + ` + beforeEach(async () => { + await factory.create('User', { + avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg', + id: 'acb2d923-f3af-479e-9f00-61b12e864666', + name: 'Matilde Hermiston', + slug: 'matilde-hermiston', + role: 'user', + email: 'test@example.org', + password: '1234' + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('authenticated', () => { + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('rejects empty string', async () => { + const variables = { url: '' } + await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') + }) + + it('validates URLs', async () => { + const variables = { url: 'not-a-url' } + await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') + }) + }) +}) \ No newline at end of file diff --git a/backend/src/resolvers/user_management.spec.js b/backend/src/resolvers/user_management.spec.js index ac3856c52..c059ec78e 100644 --- a/backend/src/resolvers/user_management.spec.js +++ b/backend/src/resolvers/user_management.spec.js @@ -310,31 +310,3 @@ describe('change password', () => { }) }) -describe('CreateSocialMedia', () => { - let client - let headers - const mutation = ` - mutation($input: SocialMediaInput) { - CreateSocialMedia(input: $input) { - url - } - } - ` - - describe('authenticated', () => { - beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) - - it('rejects empty string', async () => { - const variables = { input: { url: '' } } - await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') - }) - - it('validates URLs', async () => { - const variables = { input: { url: 'not-a-url' } } - await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL') - }) - }) -}) diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 3c229a8fd..94e28d0d7 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -26,7 +26,6 @@ type Mutation { disable(id: ID!): ID enable(id: ID!): ID reward(fromBadgeId: ID!, toUserId: ID!): ID - createSocialMedia(input: SocialMediaInput): SocialMedia unreward(fromBadgeId: ID!, toUserId: ID!): ID "Shout the given Type and ID" shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """ @@ -327,6 +326,3 @@ type SocialMedia { ownedBy: [User]! @relation(name: "OWNED", direction: "IN") } -input SocialMediaInput { - url: String! -} diff --git a/backend/src/server.js b/backend/src/server.js index 7b5ad31e1..0dff63635 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -10,8 +10,6 @@ import applyScalars from './bootstrap/scalars' import { getDriver } from './bootstrap/neo4j' import helmet from 'helmet' import decode from './jwt/decode' -// import ConstraintDirective from 'graphql-constraint-directive' -const ConstraintDirective = require('graphql-constraint-directive') dotenv.config() @@ -28,7 +26,6 @@ const debug = process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'tr let schema = makeAugmentedSchema({ typeDefs, - schemaDirectives: { constraint: ConstraintDirective }, resolvers, config: { query: { diff --git a/backend/yarn.lock b/backend/yarn.lock index f9f530d1b..68e6cb931 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1386,7 +1386,7 @@ apollo-link-http@~1.5.14: apollo-link-http-common "^0.2.13" tslib "^1.9.3" -apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.2, apollo-link@^1.2.3, apollo-link@^1.2.4: +apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.3, apollo-link@^1.2.4: version "1.2.11" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d" integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA== @@ -3660,14 +3660,6 @@ graphql-auth-directives@^2.1.0: graphql-tools "^4.0.4" jsonwebtoken "^8.3.0" -graphql-constraint-directive@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/graphql-constraint-directive/-/graphql-constraint-directive-1.3.0.tgz#f041cc0429155fb3f4ef6941b4add42fa4b77a3f" - integrity sha512-z3LTSUbwkvvpu6/ywccGfNg+7pXp+cjEzgioaK/QDsHIehKju1SM6KPBTDVPugSJbVXb8M80Jxt6mOC7t//SmA== - dependencies: - graphql-tools "^3.0.1" - validator "^10.2.0" - graphql-custom-directives@~0.2.14: version "0.2.14" resolved "https://registry.yarnpkg.com/graphql-custom-directives/-/graphql-custom-directives-0.2.14.tgz#88611b8cb074477020ad85af47bfe168c4c23992" @@ -3779,17 +3771,6 @@ graphql-tag@^2.9.2, graphql-tag@~2.10.1: resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.1.tgz#10aa41f1cd8fae5373eaf11f1f67260a3cad5e02" integrity sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg== -graphql-tools@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.1.1.tgz#d593358f01e7c8b1671a17b70ddb034dea9dbc50" - integrity sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg== - dependencies: - apollo-link "^1.2.2" - apollo-utilities "^1.0.1" - deprecated-decorator "^0.1.6" - iterall "^1.1.3" - uuid "^3.1.0" - graphql-tools@^4.0.0, graphql-tools@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.4.tgz#ca08a63454221fdde825fe45fbd315eb2a6d566b" @@ -7775,11 +7756,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@^10.2.0: - version "10.11.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" - integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== - vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"