diff --git a/backend/package.json b/backend/package.json index fa44daef8..0de8b69cb 100644 --- a/backend/package.json +++ b/backend/package.json @@ -41,7 +41,7 @@ ] }, "dependencies": { - "@hapi/joi": "^16.1.1", + "@hapi/joi": "^16.1.2", "@sentry/node": "^5.6.2", "activitystrea.ms": "~2.1.3", "apollo-cache-inmemory": "~1.6.3", @@ -55,38 +55,38 @@ "cheerio": "~1.0.0-rc.3", "cors": "~2.8.5", "cross-env": "~6.0.0", - "date-fns": "2.2.1", + "date-fns": "2.3.0", "debug": "~4.1.1", "dotenv": "~8.1.0", "express": "^4.17.1", "faker": "Marak/faker.js#master", - "graphql": "^14.5.6", + "graphql": "^14.5.7", "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", "graphql-middleware": "~3.0.5", "graphql-middleware-sentry": "^3.2.0", "graphql-shield": "~6.1.0", "graphql-tag": "~2.10.1", - "helmet": "~3.21.0", + "helmet": "~3.21.1", "jsonwebtoken": "~8.5.1", "linkifyjs": "~2.1.8", "lodash": "~4.17.14", "merge-graphql-schemas": "^1.7.0", "metascraper": "^4.10.3", - "metascraper-audio": "^5.7.4", + "metascraper-audio": "^5.7.5", "metascraper-author": "^5.7.4", "metascraper-clearbit-logo": "^5.3.0", "metascraper-date": "^5.7.4", "metascraper-description": "^5.7.4", - "metascraper-image": "^5.7.4", + "metascraper-image": "^5.7.5", "metascraper-lang": "^5.7.4", "metascraper-lang-detector": "^4.8.5", - "metascraper-logo": "^5.7.4", + "metascraper-logo": "^5.7.5", "metascraper-publisher": "^5.7.4", "metascraper-soundcloud": "^5.7.4", "metascraper-title": "^5.7.4", - "metascraper-url": "^5.7.4", - "metascraper-video": "^5.7.4", + "metascraper-url": "^5.7.5", + "metascraper-video": "^5.7.5", "metascraper-youtube": "^5.7.4", "minimatch": "^3.0.4", "mustache": "^3.0.3", diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 116794fda..ce98090ad 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -152,8 +152,8 @@ const permissions = shield( // RemoveBadgeRewarded: isAdmin, reward: isAdmin, unreward: isAdmin, - follow: isAuthenticated, - unfollow: isAuthenticated, + followUser: isAuthenticated, + unfollowUser: isAuthenticated, shout: isAuthenticated, unshout: isAuthenticated, changePassword: isAuthenticated, diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 736b7b1ab..311e64350 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -4,7 +4,7 @@ module.exports = { id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests actorId: { type: 'string', allow: [null] }, name: { type: 'string', disallow: [null], min: 3 }, - slug: 'string', + slug: { type: 'string', regex: /^[a-z0-9_-]+$/, lowercase: true }, encryptedPassword: 'string', avatar: { type: 'string', allow: [null] }, coverImg: { type: 'string', allow: [null] }, diff --git a/backend/src/models/User.spec.js b/backend/src/models/User.spec.js index e00136970..7c4a26c55 100644 --- a/backend/src/models/User.spec.js +++ b/backend/src/models/User.spec.js @@ -18,3 +18,67 @@ describe('role', () => { ) }) }) + +describe('slug', () => { + it('normalizes to lowercase letters', async () => { + const user = await instance.create('User', { slug: 'Matt' }) + await expect(user.toJson()).resolves.toEqual( + expect.objectContaining({ + slug: 'matt', + }), + ) + }) + + it('must be unique', async done => { + await instance.create('User', { slug: 'Matt' }) + try { + await expect(instance.create('User', { slug: 'Matt' })).rejects.toThrow('already exists') + done() + } catch (error) { + throw new Error(` + ${error} + + Probably your database has no unique constraints! + + To see all constraints go to http://localhost:7474/browser/ and + paste the following: + \`\`\` + CALL db.constraints(); + \`\`\` + + Learn how to setup the database here: + https://docs.human-connection.org/human-connection/neo4j + `) + } + }) + + describe('characters', () => { + const createUser = attrs => { + return instance.create('User', attrs).then(user => user.toJson()) + } + + it('-', async () => { + await expect(createUser({ slug: 'matt-rider' })).resolves.toMatchObject({ + slug: 'matt-rider', + }) + }) + + it('_', async () => { + await expect(createUser({ slug: 'matt_rider' })).resolves.toMatchObject({ + slug: 'matt_rider', + }) + }) + + it(' ', async () => { + await expect(createUser({ slug: 'matt rider' })).rejects.toThrow( + /fails to match the required pattern/, + ) + }) + + it('ä', async () => { + await expect(createUser({ slug: 'mätt' })).rejects.toThrow( + /fails to match the required pattern/, + ) + }) + }) +}) diff --git a/backend/src/schema/resolvers/follow.js b/backend/src/schema/resolvers/follow.js index f0a98e1f7..ada417cff 100644 --- a/backend/src/schema/resolvers/follow.js +++ b/backend/src/schema/resolvers/follow.js @@ -4,24 +4,24 @@ const neode = getNeode() export default { Mutation: { - follow: async (_object, params, context, _resolveInfo) => { - const { id: followedId, type } = params + followUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params const { user: currentUser } = context - if (type === 'User' && currentUser.id === followedId) { + if (currentUser.id === followedUserId) { return null } - const [user, followedNode] = await Promise.all([ + const [user, followedUser] = await Promise.all([ neode.find('User', currentUser.id), - neode.find(type, followedId), + neode.find('User', followedUserId), ]) - await user.relateTo(followedNode, 'following') - return followedNode.toJson() + await user.relateTo(followedUser, 'following') + return followedUser.toJson() }, - unfollow: async (_object, params, context, _resolveInfo) => { - const { id: followedId, type } = params + unfollowUser: async (_object, params, context, _resolveInfo) => { + const { id: followedUserId } = params const { user: currentUser } = context /* @@ -30,15 +30,14 @@ export default { * However, pure cypher query looks cleaner IMO */ await neode.cypher( - `MATCH (user:User {id: $currentUser.id})-[relation:FOLLOWS]->(node {id: $followedId}) - WHERE $type IN labels(node) + `MATCH (user:User {id: $currentUser.id})-[relation:FOLLOWS]->(followedUser:User {id: $followedUserId}) DELETE relation RETURN COUNT(relation) > 0 as isFollowed`, - { followedId, type, currentUser }, + { followedUserId, currentUser }, ) - const followedNode = await neode.find(type, followedId) - return followedNode.toJson() + const followedUser = await neode.find('User', followedUserId) + return followedUser.toJson() }, }, } diff --git a/backend/src/schema/resolvers/follow.spec.js b/backend/src/schema/resolvers/follow.spec.js index 1aa146075..7b801d377 100644 --- a/backend/src/schema/resolvers/follow.spec.js +++ b/backend/src/schema/resolvers/follow.spec.js @@ -1,11 +1,12 @@ import { createTestClient } from 'apollo-server-testing' import Factory from '../../seed/factories' -import { getDriver } from '../../bootstrap/neo4j' +import { getDriver, neode as getNeode } from '../../bootstrap/neo4j' import createServer from '../../server' import { gql } from '../../jest/helpers' const factory = Factory() const driver = getDriver() +const neode = getNeode() let query let mutate @@ -16,8 +17,8 @@ let user2 let variables const mutationFollowUser = gql` - mutation($id: ID!, $type: FollowTypeEnum) { - follow(id: $id, type: $type) { + mutation($id: ID!) { + followUser(id: $id) { name followedBy { id @@ -29,8 +30,8 @@ const mutationFollowUser = gql` ` const mutationUnfollowUser = gql` - mutation($id: ID!, $type: FollowTypeEnum) { - unfollow(id: $id, type: $type) { + mutation($id: ID!) { + unfollowUser(id: $id) { name followedBy { id @@ -52,10 +53,12 @@ const userQuery = gql` } ` -beforeAll(() => { +beforeAll(async () => { + await factory.cleanDatabase() const { server } = createServer({ context: () => ({ driver, + neode, user: authenticatedUser, cypherParams: { currentUserId: authenticatedUser ? authenticatedUser.id : null, @@ -87,7 +90,7 @@ beforeEach(async () => { .then(user => user.toJson()) authenticatedUser = user1 - variables = { id: user2.id, type: 'User' } + variables = { id: user2.id } }) afterEach(async () => { @@ -99,71 +102,85 @@ describe('follow', () => { describe('unauthenticated follow', () => { test('throws authorization error', async () => { authenticatedUser = null - const { errors, data } = await mutate({ - mutation: mutationFollowUser, - variables, + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { followUser: null }, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - expect(data).toMatchObject({ follow: null }) }) }) test('I can follow another user', async () => { - const { data: result } = await mutate({ - mutation: mutationFollowUser, - variables, - }) const expectedUser = { name: user2.name, followedBy: [{ id: user1.id, name: user1.name }], followedByCurrentUser: true, } - expect(result).toMatchObject({ follow: expectedUser }) + await expect( + mutate({ + mutation: mutationFollowUser, + variables, + }), + ).resolves.toMatchObject({ + data: { followUser: expectedUser }, + errors: undefined, + }) }) test('I can`t follow myself', async () => { variables.id = user1.id - const { data: result } = await mutate({ mutation: mutationFollowUser, variables }) - const expectedResult = { follow: null } - expect(result).toMatchObject(expectedResult) - - const { data } = await query({ - query: userQuery, - variables: { id: user1.id }, + await expect(mutate({ mutation: mutationFollowUser, variables })).resolves.toMatchObject({ + data: { followUser: null }, + errors: undefined, }) + const expectedUser = { followedBy: [], followedByCurrentUser: false, } - expect(data).toMatchObject({ User: [expectedUser] }) + await expect( + query({ + query: userQuery, + variables: { id: user1.id }, + }), + ).resolves.toMatchObject({ + data: { + User: [expectedUser], + }, + errors: undefined, + }) }) }) describe('unfollow user', () => { beforeEach(async () => { - variables = { - id: user2.id, - type: 'User', - } + variables = { id: user2.id } await mutate({ mutation: mutationFollowUser, variables }) }) describe('unauthenticated follow', () => { test('throws authorization error', async () => { authenticatedUser = null - const { errors, data } = await mutate({ mutation: mutationUnfollowUser, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - expect(data).toMatchObject({ unfollow: null }) + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: null }, + errors: [{ message: 'Not Authorised!' }], + }) }) }) - it('I can unfollow a user', async () => { - const { data: result } = await mutate({ mutation: mutationUnfollowUser, variables }) + test('I can unfollow a user', async () => { const expectedUser = { name: user2.name, followedBy: [], followedByCurrentUser: false, } - expect(result).toMatchObject({ unfollow: expectedUser }) + await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ + data: { unfollowUser: expectedUser }, + errors: undefined, + }) }) }) }) diff --git a/backend/src/schema/resolvers/moderation.spec.js b/backend/src/schema/resolvers/moderation.spec.js index 3107a5799..2ac0dd934 100644 --- a/backend/src/schema/resolvers/moderation.spec.js +++ b/backend/src/schema/resolvers/moderation.spec.js @@ -1,420 +1,349 @@ -import { GraphQLClient } from 'graphql-request' +import { createTestClient } from 'apollo-server-testing' import Factory from '../../seed/factories' -import { host, login, gql } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { gql } from '../../jest/helpers' +import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import createServer from '../../server' -let client const factory = Factory() -const instance = neode() -const categoryIds = ['cat9'] +const neode = getNeode() +const driver = getDriver() -const setupAuthenticateClient = params => { - const authenticateClient = async () => { - await factory.create('User', params) - const headers = await login(params) - client = new GraphQLClient(host, { headers }) +let query, mutate, authenticatedUser, variables, moderator, nonModerator + +const disableMutation = gql` + mutation($id: ID!) { + disable(id: $id) } - return authenticateClient -} - -let createResource -let authenticateClient -let createPostVariables -let createCommentVariables - -beforeEach(async () => { - createResource = () => {} - authenticateClient = () => { - client = new GraphQLClient(host) +` +const enableMutation = gql` + mutation($id: ID!) { + enable(id: $id) } - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) -}) +` -const setup = async () => { - await createResource() - await authenticateClient() -} - -afterEach(async () => { - await factory.cleanDatabase() -}) - -describe('disable', () => { - const mutation = gql` - mutation($id: ID!) { - disable(id: $id) +const commentQuery = gql` + query($id: ID!) { + Comment(id: $id) { + id + disabled + disabledBy { + id + } } - ` - let variables - - beforeEach(() => { - // our defaul set of variables - variables = { - id: 'blabla', - } - }) - - const action = async () => { - return client.request(mutation, variables) } +` - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') +const postQuery = gql` + query($id: ID) { + Post(id: $id) { + id + disabled + disabledBy { + id + } + } + } +` + +describe('moderate resources', () => { + beforeAll(() => { + authenticatedUser = undefined + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + mutate = createTestClient(server).mutate + query = createTestClient(server).query }) - describe('authenticated', () => { + beforeEach(async () => { + variables = {} + authenticatedUser = null + moderator = await factory.create('User', { + id: 'moderator-id', + name: 'Moderator', + email: 'moderator@example.org', + password: '1234', + role: 'moderator', + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + describe('disable', () => { beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - email: 'user@example.org', - password: '1234', + variables = { + id: 'some-resource', + } + }) + describe('unauthenticated', () => { + it('throws authorization error', async () => { + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) }) }) - - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') - }) - - describe('as moderator', () => { - beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - id: 'u7', - email: 'moderator@example.org', - password: '1234', - role: 'moderator', + describe('authenticated', () => { + describe('non moderator', () => { + beforeEach(async () => { + nonModerator = await factory.create('User', { + id: 'non-moderator', + name: 'Non Moderator', + email: 'non.moderator@example.org', + password: '1234', + }) + authenticatedUser = await nonModerator.toJson() + }) + it('throws authorization error', async () => { + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) }) }) - describe('on something that is not a (Comment|Post|User) ', () => { + describe('moderator', () => { beforeEach(async () => { - variables = { - id: 't23', - } - createResource = () => { - return Promise.all([factory.create('Tag', { id: 't23' })]) - } + authenticatedUser = await moderator.toJson() }) - it('returns null', async () => { - const expected = { disable: null } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - }) + describe('moderate a resource that is not a (Comment|Post|User) ', () => { + beforeEach(async () => { + variables = { + id: 'sample-tag-id', + } + await factory.create('Tag', { id: 'sample-tag-id' }) + }) - describe('on a comment', () => { - beforeEach(async () => { - variables = { - id: 'c47', - } - createPostVariables = { - id: 'p3', - title: 'post to comment on', - content: 'please comment on me', - categoryIds, - } - createCommentVariables = { - id: 'c47', - postId: 'p3', - content: 'this comment was created for this post', - } - createResource = async () => { - await factory.create('User', { - id: 'u45', - email: 'commenter@example.org', - password: '1234', + it('returns null', async () => { + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: null }, }) - const asAuthenticatedUser = await factory.authenticateAs({ - email: 'commenter@example.org', - password: '1234', + }) + }) + + describe('moderate a comment', () => { + beforeEach(async () => { + variables = {} + await factory.create('Comment', { + id: 'comment-id', }) - await asAuthenticatedUser.create('Post', createPostVariables) - await asAuthenticatedUser.create('Comment', createCommentVariables) - } + }) + + it('returns disabled resource id', async () => { + variables = { id: 'comment-id' } + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'comment-id' }, + errors: undefined, + }) + }) + + it('changes .disabledBy', async () => { + variables = { id: 'comment-id' } + const before = { data: { Comment: [{ id: 'comment-id', disabledBy: null }] } } + const expected = { + data: { Comment: [{ id: 'comment-id', disabledBy: { id: 'moderator-id' } }] }, + } + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(before) + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'comment-id' }, + }) + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected) + }) + + it('updates .disabled on comment', async () => { + variables = { id: 'comment-id' } + const before = { data: { Comment: [{ id: 'comment-id', disabled: false }] } } + const expected = { data: { Comment: [{ id: 'comment-id', disabled: true }] } } + + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(before) + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'comment-id' }, + }) + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected) + }) }) - it('returns disabled resource id', async () => { - const expected = { disable: 'c47' } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - - it('changes .disabledBy', async () => { - const before = { Comment: [{ id: 'c47', disabledBy: null }] } - const expected = { Comment: [{ id: 'c47', disabledBy: { id: 'u7' } }] } - - await setup() - await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( - before, - ) - await action() - await expect( - client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(expected) - }) - - it('updates .disabled on comment', async () => { - const before = { Comment: [{ id: 'c47', disabled: false }] } - const expected = { Comment: [{ id: 'c47', disabled: true }] } - - await setup() - await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(before) - await action() - await expect( - client.request('{ Comment(disabled: true) { id disabled } }'), - ).resolves.toEqual(expected) - }) - }) - - describe('on a post', () => { - beforeEach(async () => { - variables = { - id: 'p9', - } - - createResource = async () => { - await factory.create('User', { email: 'author@example.org', password: '1234' }) - await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) + describe('moderate a post', () => { + beforeEach(async () => { + variables = {} await factory.create('Post', { - id: 'p9', // that's the ID we will look for - categoryIds, + id: 'sample-post-id', }) - } + }) + + it('returns disabled resource id', async () => { + variables = { id: 'sample-post-id' } + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'sample-post-id' }, + }) + }) + + it('changes .disabledBy', async () => { + variables = { id: 'sample-post-id' } + const before = { data: { Post: [{ id: 'sample-post-id', disabledBy: null }] } } + const expected = { + data: { Post: [{ id: 'sample-post-id', disabledBy: { id: 'moderator-id' } }] }, + } + + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(before) + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'sample-post-id' }, + }) + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected) + }) + + it('updates .disabled on post', async () => { + const before = { data: { Post: [{ id: 'sample-post-id', disabled: false }] } } + const expected = { data: { Post: [{ id: 'sample-post-id', disabled: true }] } } + variables = { id: 'sample-post-id' } + + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(before) + await expect(mutate({ mutation: disableMutation, variables })).resolves.toMatchObject({ + data: { disable: 'sample-post-id' }, + }) + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected) + }) + }) + }) + }) + }) + + describe('enable', () => { + describe('unautenticated user', () => { + it('throws authorization error', async () => { + variables = { id: 'sample-post-id' } + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('authenticated user', () => { + describe('non moderator', () => { + beforeEach(async () => { + nonModerator = await factory.create('User', { + id: 'non-moderator', + name: 'Non Moderator', + email: 'non.moderator@example.org', + password: '1234', + }) + authenticatedUser = await nonModerator.toJson() + }) + it('throws authorization error', async () => { + variables = { id: 'sample-post-id' } + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + }) + }) + }) + + describe('moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + }) + describe('moderate a resource that is not a (Comment|Post|User) ', () => { + beforeEach(async () => { + await Promise.all([factory.create('Tag', { id: 'sample-tag-id' })]) + }) + + it('returns null', async () => { + await expect( + mutate({ mutation: enableMutation, variables: { id: 'sample-tag-id' } }), + ).resolves.toMatchObject({ + data: { enable: null }, + }) + }) }) - it('returns disabled resource id', async () => { - const expected = { disable: 'p9' } - await setup() - await expect(action()).resolves.toEqual(expected) + describe('moderate a comment', () => { + beforeEach(async () => { + variables = { id: 'comment-id' } + await factory.create('Comment', { + id: 'comment-id', + }) + await mutate({ mutation: disableMutation, variables }) + }) + + it('returns enabled resource id', async () => { + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'comment-id' }, + errors: undefined, + }) + }) + + it('changes .disabledBy', async () => { + const expected = { + data: { Comment: [{ id: 'comment-id', disabledBy: null }] }, + errors: undefined, + } + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'comment-id' }, + errors: undefined, + }) + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected) + }) + + it('updates .disabled on comment', async () => { + const expected = { + data: { Comment: [{ id: 'comment-id', disabled: false }] }, + errors: undefined, + } + + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'comment-id' }, + errors: undefined, + }) + await expect(query({ query: commentQuery, variables })).resolves.toMatchObject(expected) + }) }) - it('changes .disabledBy', async () => { - const before = { Post: [{ id: 'p9', disabledBy: null }] } - const expected = { Post: [{ id: 'p9', disabledBy: { id: 'u7' } }] } + describe('moderate a post', () => { + beforeEach(async () => { + variables = { id: 'post-id' } + await factory.create('Post', { + id: 'post-id', + }) + await mutate({ mutation: disableMutation, variables }) + }) - await setup() - await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( - before, - ) - await action() - await expect( - client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(expected) - }) + it('returns enabled resource id', async () => { + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'post-id' }, + errors: undefined, + }) + }) - it('updates .disabled on post', async () => { - const before = { Post: [{ id: 'p9', disabled: false }] } - const expected = { Post: [{ id: 'p9', disabled: true }] } + it('changes .disabledBy', async () => { + const expected = { + data: { Post: [{ id: 'post-id', disabledBy: null }] }, + errors: undefined, + } + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'post-id' }, + errors: undefined, + }) + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected) + }) - await setup() - await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(before) - await action() - await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( - expected, - ) - }) - }) - }) - }) -}) - -describe('enable', () => { - const mutation = gql` - mutation($id: ID!) { - enable(id: $id) - } - ` - let variables - - const action = async () => { - return client.request(mutation, variables) - } - - beforeEach(() => { - // our defaul set of variables - variables = { - id: 'blabla', - } - }) - - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') - }) - - describe('authenticated', () => { - beforeEach(() => { - authenticateClient = setupAuthenticateClient({ - email: 'user@example.org', - password: '1234', - }) - }) - - it('throws authorization error', async () => { - await setup() - await expect(action()).rejects.toThrow('Not Authorised') - }) - - describe('as moderator', () => { - beforeEach(async () => { - authenticateClient = setupAuthenticateClient({ - role: 'moderator', - email: 'someuser@example.org', - password: '1234', - }) - }) - - describe('on something that is not a (Comment|Post|User) ', () => { - beforeEach(async () => { - variables = { - id: 't23', - } - createResource = () => { - // we cannot create a :DISABLED relationship here - return Promise.all([factory.create('Tag', { id: 't23' })]) - } - }) - - it('returns null', async () => { - const expected = { enable: null } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - }) - - describe('on a comment', () => { - beforeEach(async () => { - variables = { - id: 'c456', - } - createPostVariables = { - id: 'p9', - title: 'post to comment on', - content: 'please comment on me', - categoryIds, - } - createCommentVariables = { - id: 'c456', - postId: 'p9', - content: 'this comment was created for this post', - } - createResource = async () => { - await factory.create('User', { - id: 'u123', - email: 'author@example.org', - password: '1234', - }) - const asAuthenticatedUser = await factory.authenticateAs({ - email: 'author@example.org', - password: '1234', - }) - await asAuthenticatedUser.create('Post', createPostVariables) - await asAuthenticatedUser.create('Comment', createCommentVariables) - - const disableMutation = gql` - mutation { - disable(id: "c456") - } - ` - await factory.mutate(disableMutation) // that's we want to delete - } - }) - - it('returns disabled resource id', async () => { - const expected = { enable: 'c456' } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - - it('changes .disabledBy', async () => { - const before = { Comment: [{ id: 'c456', disabledBy: { id: 'u123' } }] } - const expected = { Comment: [{ id: 'c456', disabledBy: null }] } - - await setup() - await expect( - client.request('{ Comment(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(before) - await action() - await expect(client.request('{ Comment { id, disabledBy { id } } }')).resolves.toEqual( - expected, - ) - }) - - it('updates .disabled on post', async () => { - const before = { Comment: [{ id: 'c456', disabled: true }] } - const expected = { Comment: [{ id: 'c456', disabled: false }] } - - await setup() - await expect( - client.request('{ Comment(disabled: true) { id disabled } }'), - ).resolves.toEqual(before) - await action() // this updates .disabled - await expect(client.request('{ Comment { id disabled } }')).resolves.toEqual(expected) - }) - }) - - describe('on a post', () => { - beforeEach(async () => { - variables = { - id: 'p9', - } - - createResource = async () => { - await factory.create('User', { - id: 'u123', - email: 'author@example.org', - password: '1234', - }) - await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) - await factory.create('Post', { - id: 'p9', // that's the ID we will look for - categoryIds, - }) - - const disableMutation = gql` - mutation { - disable(id: "p9") - } - ` - await factory.mutate(disableMutation) // that's we want to delete - } - }) - - it('returns disabled resource id', async () => { - const expected = { enable: 'p9' } - await setup() - await expect(action()).resolves.toEqual(expected) - }) - - it('changes .disabledBy', async () => { - const before = { Post: [{ id: 'p9', disabledBy: { id: 'u123' } }] } - const expected = { Post: [{ id: 'p9', disabledBy: null }] } - - await setup() - await expect( - client.request('{ Post(disabled: true) { id, disabledBy { id } } }'), - ).resolves.toEqual(before) - await action() - await expect(client.request('{ Post { id, disabledBy { id } } }')).resolves.toEqual( - expected, - ) - }) - - it('updates .disabled on post', async () => { - const before = { Post: [{ id: 'p9', disabled: true }] } - const expected = { Post: [{ id: 'p9', disabled: false }] } - - await setup() - await expect(client.request('{ Post(disabled: true) { id disabled } }')).resolves.toEqual( - before, - ) - await action() // this updates .disabled - await expect(client.request('{ Post { id disabled } }')).resolves.toEqual(expected) + it('updates .disabled on post', async () => { + const expected = { + data: { Post: [{ id: 'post-id', disabled: false }] }, + errors: undefined, + } + + await expect(mutate({ mutation: enableMutation, variables })).resolves.toMatchObject({ + data: { enable: 'post-id' }, + errors: undefined, + }) + await expect(query({ query: postQuery, variables })).resolves.toMatchObject(expected) + }) }) }) }) diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 0e4fc48f7..1b9cac102 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -192,7 +192,7 @@ describe('given some notifications', () => { it('returns only unread notifications of current user', async () => { const expected = expect.objectContaining({ data: { - notifications: [ + notifications: expect.arrayContaining([ { from: { __typename: 'Comment', @@ -209,12 +209,15 @@ describe('given some notifications', () => { read: false, createdAt: '2019-08-31T17:33:48.651Z', }, - ], + ]), }, }) - await expect( - query({ query: notificationQuery, variables: { ...variables, read: false } }), - ).resolves.toEqual(expected) + const response = await query({ + query: notificationQuery, + variables: { ...variables, read: false }, + }) + await expect(response).toMatchObject(expected) + await expect(response.data.notifications.length).toEqual(2) // double-check }) describe('if a resource gets deleted', () => { diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 620039b28..06d0e061b 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -31,10 +31,8 @@ type Mutation { shout(id: ID!, type: ShoutTypeEnum): Boolean! # Unshout the given Type and ID unshout(id: ID!, type: ShoutTypeEnum): Boolean! - # Follow the given Type and ID - follow(id: ID!, type: FollowTypeEnum): User - # Unfollow the given Type and ID - unfollow(id: ID!, type: FollowTypeEnum): User + followUser(id: ID!): User + unfollowUser(id: ID!): User } type Report { @@ -57,9 +55,6 @@ enum Deletable { enum ShoutTypeEnum { Post } -enum FollowTypeEnum { - User -} type Reward { id: ID! diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index c0a684715..ab09b438d 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -118,13 +118,12 @@ export default function Factory(options = {}) { this.lastResponse = await this.graphQLClient.request(mutation) return this }, - async follow(properties) { - const { id, type } = properties + async followUser(properties) { + const { id } = properties const mutation = ` mutation { - follow( - id: "${id}", - type: ${type} + followUser( + id: "${id}" ) } ` @@ -166,7 +165,7 @@ export default function Factory(options = {}) { result.relate.bind(result) result.mutate.bind(result) result.shout.bind(result) - result.follow.bind(result) + result.followUser.bind(result) result.invite.bind(result) result.cleanDatabase.bind(result) return result diff --git a/backend/yarn.lock b/backend/yarn.lock index 4f381e812..095b9d68f 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -766,10 +766,10 @@ "@hapi/hoek" "8.x.x" "@hapi/topo" "3.x.x" -"@hapi/joi@^16.1.1": - version "16.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.1.tgz#67a47cf46b163782ab69802b17ac4a86fb89f83e" - integrity sha512-v/XNMGNz+Nx7578Cx2bMunoQHuY4LFxRltJ6uA1LjS6LWakgPCJC4MTr1ucfCnjjbDtaQizrQx9oWXY3WcFcyw== +"@hapi/joi@^16.1.2": + version "16.1.2" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.2.tgz#c566d9e0d81d6847f7622f7d5e23adadaa2d7332" + integrity sha512-wkMIEMQQPNmat9P7zws7wO8Gon9W3NgG5Pac1m0LK8bQ1bbszofxzL0CJogAgzitk5rZZw5/txR+wOK/ioLmGw== dependencies: "@hapi/address" "^2.1.1" "@hapi/formula" "^1.2.0" @@ -971,10 +971,10 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.7.4": - version "5.7.4" - resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.4.tgz#c91c1b11ce585fa973a544a9d24c5d88d50a9354" - integrity sha512-GMLFu8j7e65n04w+dfOVF8RWOqNHCqimITtTHYSa1XdLR8vSqE2PjvSOhGoS5ELU5fRlRQKy9EOrKDeRV3/K0w== +"@metascraper/helpers@^5.7.4", "@metascraper/helpers@^5.7.5": + version "5.7.5" + resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.7.5.tgz#fb4ca0e2825f836f1398dcc85227443a36eeb84c" + integrity sha512-ayeJIJqlqeiJHYPYi7fmhjvOg7FHTjfqd57nZCLo0fkqj2exsCa788G5Ihk5qHsk1ASVOgH+flp1XeyMl1vcXQ== dependencies: audio-extensions "0.0.0" chrono-node "~1.3.11" @@ -990,7 +990,7 @@ lodash "~4.17.15" mem "~5.1.1" mime-types "~2.1.24" - normalize-url "~4.3.0" + normalize-url "~4.4.1" smartquotes "~2.3.1" title "~3.4.1" truncate "~2.1.0" @@ -2169,10 +2169,10 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bowser@2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.5.4.tgz#850fccfebde92165440279b5ab19be3c7f05cfe1" - integrity sha512-74GGwfc2nzYD19JCiA0RwCxdq7IY5jHeEaSrrgm/5kusEuK+7UK0qDG3gyzN47c4ViNyO4osaKtZE+aSV6nlpQ== +bowser@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.6.1.tgz#196599588af6f0413449c79ab3bf7a5a1bb3384f" + integrity sha512-hySGUuLhi0KetfxPZpuJOsjM0kRvCiCgPBygBkzGzJNsq/nbJmaO8QJc6xlWfeFFnMvtd/LeKkhDJGVrmVobUA== boxen@^1.2.1: version "1.3.0" @@ -2868,10 +2868,10 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-fns@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.2.1.tgz#b3f79cf56760af106050c686f4c72586a3383ee9" - integrity sha512-4V1i5CnTinjBvJpXTq7sDHD4NY6JPcl15112IeSNNLUWQOQ+kIuCvRGOFZMQZNvkadw8F9QTyZxz59rIRU6K+w== +date-fns@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.3.0.tgz#017eae725d0c46173b572da025fb5e4e534270fd" + integrity sha512-A8o+iXBVqQayl9Z39BHgb7m/zLOfhF7LK82t+n9Fq1adds1vaUn8ByVoADqWLe4OTc6BZYc/FdbdTwufNYqkJw== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" @@ -4209,10 +4209,10 @@ graphql-upload@^8.0.2: http-errors "^1.7.2" object-path "^0.11.4" -graphql@^14.2.1, graphql@^14.5.6: - version "14.5.6" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.6.tgz#3fa12173b50e6ccdef953c31c82f37c50ef58bec" - integrity sha512-zJ6Oz8P1yptV4O4DYXdArSwvmirPetDOBnGFRBl0zQEC68vNW3Ny8qo8VzMgfr+iC8PKiRYJ+f2wub41oDCoQg== +graphql@^14.2.1, graphql@^14.5.7: + version "14.5.7" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.7.tgz#8646a3fcc07922319cc3967eba4a64b32929f77f" + integrity sha512-as410RMJSUFqF8RcH2QWxZ5ioqHzsH9VWnWbaU+UnDXJ/6azMDIYPrtXCBPXd8rlunEVb7W8z6fuUnNHMbFu9A== dependencies: iterall "^1.2.2" @@ -4333,20 +4333,20 @@ helmet-crossdomain@0.4.0: resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== -helmet-csp@2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.1.tgz#39939a84ca3657ee3cba96f296169ccab02f97d5" - integrity sha512-HgdXSJ6AVyXiy5ohVGpK6L7DhjI9KVdKVB1xRoixxYKsFXFwoVqtLKgDnfe3u8FGGKf9Ml9k//C9rnncIIAmyA== +helmet-csp@2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.9.2.tgz#bec0adaf370b0f2e77267c9d8b6e33b34159c1e5" + integrity sha512-Lt5WqNfbNjEJ6ysD4UNpVktSyjEKfU9LVJ1LaFmPfYseg/xPealPfgHhtqdAdjPDopp5zbg/VWCyp4cluMIckw== dependencies: - bowser "2.5.4" + bowser "^2.6.1" camelize "1.0.0" content-security-policy-builder "2.1.0" dasherize "2.0.0" -helmet@~3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.21.0.tgz#e7c5e2ed3b8b7f42d2e387004a87198b295132cc" - integrity sha512-TS3GryQMPR7n/heNnGC0Cl3Ess30g8C6EtqZyylf+Y2/kF4lM8JinOR90rzIICsw4ymWTvji4OhDmqsqxkLrcg== +helmet@~3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.21.1.tgz#b0ab7c63fc30df2434be27e7e292a9523b3147e9" + integrity sha512-IC/54Lxvvad2YiUdgLmPlNFKLhNuG++waTF5KPYq/Feo3NNhqMFbcLAlbVkai+9q0+4uxjxGPJ9bNykG+3zZNg== dependencies: depd "2.0.0" dns-prefetch-control "0.2.0" @@ -4355,7 +4355,7 @@ helmet@~3.21.0: feature-policy "0.3.0" frameguard "3.1.0" helmet-crossdomain "0.4.0" - helmet-csp "2.9.1" + helmet-csp "2.9.2" hide-powered-by "1.1.0" hpkp "2.0.0" hsts "2.2.0" @@ -5883,12 +5883,12 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -metascraper-audio@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.7.4.tgz#a476ed484b2642060208243843dc7ef5c9eb7a3e" - integrity sha512-5M+C+tirJlR71PXymAkBnEXu8KG0+fkX3G0Dm2UO6jDEchIo+DhW2aGSgB8w9kQN80Ni8jaLjNH3+Mtwbsbbkw== +metascraper-audio@^5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.7.5.tgz#9ccdc85a2e17b6767e91ecfc964faaa83e10e917" + integrity sha512-2uE2VrsB780krOoKSGM08iquxyZmLEWNEG/8P3+wbZJ3aQA+JVTc7He/D8XMhFd93dFTpVZUNV9qLlPIjWnwnw== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.7.5" metascraper-author@^5.7.4: version "5.7.4" @@ -5919,12 +5919,12 @@ metascraper-description@^5.7.4: dependencies: "@metascraper/helpers" "^5.7.4" -metascraper-image@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.7.4.tgz#095aad47efa263c872d1762fdb3fdc1fcad62129" - integrity sha512-AZRQR9Z6BMJ/EfPG2g5XlRVrkGwiHAPJiakJl1kASHAvfqdznkW6ZjOCta1Wx76x++jjwWRxF67K/elLg8AMdg== +metascraper-image@^5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/metascraper-image/-/metascraper-image-5.7.5.tgz#fef461b706885f6a6be4141e8270318dbc66936d" + integrity sha512-n6SLTCKNugEJuZWHxEISsLOmQKlxs1Rzl+EsZzYeLKYu5fnCI7XegepOC85erofPl3OaivrKyWk3WKUN+qQ3JA== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.7.5" metascraper-lang-detector@^4.8.5: version "4.10.2" @@ -5942,12 +5942,12 @@ metascraper-lang@^5.7.4: dependencies: "@metascraper/helpers" "^5.7.4" -metascraper-logo@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.7.4.tgz#a90136718b7f827ba41249442f48a0535245bf13" - integrity sha512-SIpKMWydmVHSFjV7/exPxDx7Ydgp5n5GG0dLBNKCEuv3fHiMulrtevDlV+yk4xIGPh1CnA0hCS6mL7N/2y9ltw== +metascraper-logo@^5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/metascraper-logo/-/metascraper-logo-5.7.5.tgz#90f9fc30191a495f439e4f36d90af01fd3995a64" + integrity sha512-L+ZyJx+c7V0RyRubr6hITlnTjmEkPVJmXnWHz/bbWXEI++MA8/jI/XVsbxugcliMhdG8/UW+wANZ/uBoRHejdA== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.7.5" metascraper-publisher@^5.7.4: version "5.7.4" @@ -5973,19 +5973,19 @@ metascraper-title@^5.7.4: "@metascraper/helpers" "^5.7.4" lodash "~4.17.15" -metascraper-url@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.7.4.tgz#c2aa19d5ebd1e29d1d4154d350cc903fd1725d95" - integrity sha512-ApmaiKny0stNXoGABVDFaXpfK2J5cO/wTUuiaS/bsPWwnwn9TFfdAzatEdzDM6pq77pbKWI6CkdEpeNE5b10/g== +metascraper-url@^5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/metascraper-url/-/metascraper-url-5.7.5.tgz#f503820e2429036b26f5dad0b55f0e430bd49a6d" + integrity sha512-yY1HUiqZf7PkTMN4DeUfxDhtnMCzqyj7IvGbAVM0dHWzxC+s+RNM5NR1jo+DxVIVUxRygo3REQHwVA0Uu1CATg== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.7.5" -metascraper-video@^5.7.4: - version "5.7.4" - resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.7.4.tgz#33895606b5bde9199e02c726811925a52f9aefb0" - integrity sha512-8Rm+y0MW+nGS5A5Z08ZAkcwBif60IGNxf7w0D83i1lw5/8K/g/WpGK0NeT8UuVha0ZHXMQcY1TQOhZO56dpAbA== +metascraper-video@^5.7.5: + version "5.7.5" + resolved "https://registry.yarnpkg.com/metascraper-video/-/metascraper-video-5.7.5.tgz#15dd760fe26acb21cac7ced60f1ad508b0f130d1" + integrity sha512-LZFSttRIvUz9yEM17Z8CN0XI925CFTrV6pHMMSglD3bQH4qtrne1d+xXDUz6riPhBuR80BA5Xb9OrpRPSNCK2w== dependencies: - "@metascraper/helpers" "^5.7.4" + "@metascraper/helpers" "^5.7.5" lodash "~4.17.15" metascraper-youtube@^5.7.4: @@ -6409,7 +6409,7 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0, normalize-url@~4.3.0: +normalize-url@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee" integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ== @@ -6419,6 +6419,11 @@ normalize-url@~4.2.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.2.0.tgz#e747f16b58e6d7f391495fd86415fa04ec7c9897" integrity sha512-n69+KXI+kZApR+sPwSkoAXpGlNkaiYyoHHqKOFPjJWvwZpew/EjKvuPE4+tStNgb42z5yLtdakgZCQI+LalSPg== +normalize-url@~4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.4.1.tgz#81e9c153b0ad5743755696f2aa20488d48e962b6" + integrity sha512-rjH3yRt0Ssx19mUwS0hrDUOdG9VI+oRLpLHJ7tXRdjcuQ7v7wo6qPvOZppHRrqfslTKr0L2yBhjj4UXd7c3cQg== + npm-bundled@^1.0.1: version "1.0.6" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" diff --git a/cypress/integration/common/settings.js b/cypress/integration/common/settings.js index b32924f6a..563d6a733 100644 --- a/cypress/integration/common/settings.js +++ b/cypress/integration/common/settings.js @@ -18,6 +18,8 @@ When('I save {string} as my new name', name => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') }) When('I save {string} as my location', location => { @@ -28,6 +30,8 @@ When('I save {string} as my location', location => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') myLocation = location }) @@ -38,6 +42,8 @@ When('I have the following self-description:', text => { cy.get('[type=submit]') .click() .not('[disabled]') + cy.get('.iziToast-message') + .should('contain', 'Your data was successfully updated') aboutMeText = text }) diff --git a/package.json b/package.json index 841dd171a..d71a399b3 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "bcryptjs": "^2.4.3", - "codecov": "^3.5.0", + "codecov": "^3.6.1", "cross-env": "^6.0.0", "cypress": "^3.4.1", "cypress-cucumber-preprocessor": "^1.16.0", diff --git a/webapp/.eslintignore b/webapp/.eslintignore index d56900caf..be90fc8e3 100644 --- a/webapp/.eslintignore +++ b/webapp/.eslintignore @@ -3,3 +3,4 @@ build .nuxt styleguide/ **/*.min.js +static/sw.js diff --git a/webapp/.gitignore b/webapp/.gitignore index f8c980f7c..dca219bb5 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -84,3 +84,5 @@ cypress.env.json # Apple macOS folder attribute file .DS_Store + +sw.* diff --git a/webapp/components/Comment.spec.js b/webapp/components/Comment/Comment.spec.js similarity index 99% rename from webapp/components/Comment.spec.js rename to webapp/components/Comment/Comment.spec.js index b2d6d060a..381d49bc2 100644 --- a/webapp/components/Comment.spec.js +++ b/webapp/components/Comment/Comment.spec.js @@ -30,6 +30,7 @@ describe('Comment.vue', () => { }, $filters: { truncate: a => a, + removeHtml: a => a, }, $apollo: { mutate: jest.fn().mockResolvedValue({ diff --git a/webapp/components/Comment/Comment.story.js b/webapp/components/Comment/Comment.story.js new file mode 100644 index 000000000..291b6cb11 --- /dev/null +++ b/webapp/components/Comment/Comment.story.js @@ -0,0 +1,54 @@ +import { storiesOf } from '@storybook/vue' +import { withA11y } from '@storybook/addon-a11y' +import Comment from './Comment' +import helpers from '~/storybook/helpers' + +helpers.init() + +const comment = { + id: '5d42a2277f2725002a449cb3', + content: + '

Thank you all!

@wolfgang-huss @robert-schafer @greg @human-connection

watch my video

I think we can all learn a lot from Alex\'s video :)

It\'s really great stuff!!

Please give him a big smiley face emoticon :D

', + contentExcerpt: + '

Thank you all!

@wolfgang-huss @robert-schafer @greg @human-connection

watch my video

I think we can all learn a lot from Alex\'s video :)

It\'s really great stuff!!

Please give him a …

', + createdAt: '2019-08-01T08:26:15.839Z', + updatedAt: '2019-08-01T08:26:15.839Z', + deleted: false, + disabled: false, + author: { + id: '1', + avatar: + 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg', + slug: 'jenny-rostock', + name: 'Rainer Unsinn', + disabled: false, + deleted: false, + contributionsCount: 25, + shoutedCount: 5, + commentedCount: 39, + followedByCount: 2, + followedByCurrentUser: true, + location: null, + badges: [ + { + id: 'indiegogo_en_bear', + icon: '/img/badges/indiegogo_en_bear.svg', + __typename: 'Badge', + }, + ], + __typename: 'User', + }, + __typename: 'Comment', +} + +storiesOf('Comment', module) + .addDecorator(withA11y) + .addDecorator(helpers.layout) + .add('Basic comment', () => ({ + components: { Comment }, + store: helpers.store, + data: () => ({ + comment, + }), + template: ``, + })) diff --git a/webapp/components/Comment.vue b/webapp/components/Comment/Comment.vue similarity index 88% rename from webapp/components/Comment.vue rename to webapp/components/Comment/Comment.vue index f7ce0117b..a1bd7dd34 100644 --- a/webapp/components/Comment.vue +++ b/webapp/components/Comment/Comment.vue @@ -11,7 +11,7 @@
- + @@ -21,14 +21,12 @@ resource-type="comment" :resource="comment" :modalsData="menuModalsData" - style="float-right" + class="float-right" :is-owner="isAuthor(author.id)" @showEditCommentMenu="editCommentMenu" /> - -
-
+
@@ -59,10 +55,14 @@
- +
- + {{ $t('comment.show.less') }} @@ -169,6 +169,10 @@ export default { } diff --git a/webapp/components/CommentForm/CommentForm.vue b/webapp/components/CommentForm/CommentForm.vue index 6f1c7d931..6cdd08af3 100644 --- a/webapp/components/CommentForm/CommentForm.vue +++ b/webapp/components/CommentForm/CommentForm.vue @@ -123,6 +123,7 @@ export default { data: { UpdateComment }, } = res this.$emit('updateComment', UpdateComment) + this.$emit('collapse') this.$toast.success(this.$t('post.comment.updated')) this.disabled = false this.closeEditWindow() diff --git a/webapp/components/CommentList/CommentList.spec.js b/webapp/components/CommentList/CommentList.spec.js index c066405e7..4d382b36d 100644 --- a/webapp/components/CommentList/CommentList.spec.js +++ b/webapp/components/CommentList/CommentList.spec.js @@ -1,6 +1,5 @@ import { config, mount, createLocalVue } from '@vue/test-utils' import CommentList from './CommentList' -import Empty from '~/components/Empty' import Vuex from 'vuex' import Styleguide from '@human-connection/styleguide' import Filters from '~/plugins/vue-filters' @@ -41,6 +40,7 @@ describe('CommentList.vue', () => { $t: jest.fn(), $filters: { truncate: a => a, + removeHtml: a => a, }, $apollo: { queries: { @@ -69,11 +69,6 @@ describe('CommentList.vue', () => { wrapper = Wrapper() }) - it('displays a message icon when there are no comments to display', () => { - propsData.post.comments = [] - expect(Wrapper().findAll(Empty)).toHaveLength(1) - }) - it('displays a comments counter', () => { expect(wrapper.find('span.ds-tag').text()).toEqual('1') }) diff --git a/webapp/components/CommentList/CommentList.vue b/webapp/components/CommentList/CommentList.vue index 17da8dda4..6061847d7 100644 --- a/webapp/components/CommentList/CommentList.vue +++ b/webapp/components/CommentList/CommentList.vue @@ -12,7 +12,7 @@ > {{ post.comments.length }} -   Comments + {{ $t('common.comment', null, 0) }} @@ -26,17 +26,14 @@ @updateComment="updateCommentList" />
-
+ + diff --git a/webapp/components/EmotionsButton/EmotionsButton.vue b/webapp/components/EmotionsButton/EmotionsButton.vue index b4849c31a..7536c1632 100644 --- a/webapp/components/EmotionsButton/EmotionsButton.vue +++ b/webapp/components/EmotionsButton/EmotionsButton.vue @@ -9,7 +9,6 @@

{{ PostsEmotionsCountByEmotion[emotion] }}x

- {{ $t('contribution.emotions-label.emoted') }}
diff --git a/webapp/components/FollowButton.vue b/webapp/components/FollowButton.vue index 5275035e5..e1cad8c8e 100644 --- a/webapp/components/FollowButton.vue +++ b/webapp/components/FollowButton.vue @@ -73,7 +73,7 @@ export default { variables: { id: this.followId }, }) - const followedUser = follow ? data.follow : data.unfollow + const followedUser = follow ? data.followUser : data.unfollowUser this.$emit('update', followedUser) } catch { optimisticResult.followedByCurrentUser = !follow diff --git a/webapp/components/Registration/CreateUserAccount.vue b/webapp/components/Registration/CreateUserAccount.vue index ebcfb9691..b5711873c 100644 --- a/webapp/components/Registration/CreateUserAccount.vue +++ b/webapp/components/Registration/CreateUserAccount.vue @@ -8,80 +8,90 @@ - - + + @@ -157,13 +167,9 @@ export default { } - diff --git a/webapp/components/utils/UniqueSlugForm.js b/webapp/components/utils/UniqueSlugForm.js new file mode 100644 index 000000000..c363fa608 --- /dev/null +++ b/webapp/components/utils/UniqueSlugForm.js @@ -0,0 +1,36 @@ +import { debounce } from 'lodash' +import { checkSlugAvailableQuery } from '~/graphql/User.js' + +export default function UniqueSlugForm({ translate, apollo, currentUser }) { + return { + formSchema: { + slug: [ + { + type: 'string', + required: true, + pattern: /^[a-z0-9_-]+$/, + message: translate('settings.validation.slug.regex'), + }, + { + asyncValidator(rule, value, callback) { + debounce(() => { + const variables = { slug: value } + apollo.query({ query: checkSlugAvailableQuery, variables }).then(response => { + const { + data: { User }, + } = response + const existingSlug = User && User[0] && User[0].slug + const available = !existingSlug || existingSlug === currentUser.slug + if (!available) { + callback(new Error(translate('settings.validation.slug.alreadyTaken'))) + } else { + callback() + } + }) + }, 500)() + }, + }, + ], + }, + } +} diff --git a/webapp/components/utils/UniqueSlugForm.spec.js b/webapp/components/utils/UniqueSlugForm.spec.js new file mode 100644 index 000000000..de0e3fee6 --- /dev/null +++ b/webapp/components/utils/UniqueSlugForm.spec.js @@ -0,0 +1,80 @@ +import UniqueSlugForm from './UniqueSlugForm' +import Schema from 'async-validator' + +let translate +let apollo +let currentUser + +beforeEach(() => { + translate = jest.fn(() => 'Validation error') + apollo = { + query: jest.fn().mockResolvedValue({ data: { User: [] } }), + } + currentUser = null +}) + +describe('UniqueSlugForm', () => { + let validate = object => { + const { formSchema } = UniqueSlugForm({ translate, apollo, currentUser }) + const validator = new Schema(formSchema) + return validator.validate(object, { suppressWarning: true }).catch(({ errors }) => { + throw new Error(errors[0].message) + }) + } + + describe('regex', () => { + describe('non URL-safe characters, e.g. whitespaces', () => { + it('rejects', async () => { + await expect(validate({ slug: 'uh oh' })).rejects.toThrow('Validation error') + }) + }) + + describe('alphanumeric, hyphens or underscores', () => { + it('validates', async () => { + await expect(validate({ slug: '_all-right_' })).resolves.toBeUndefined() + }) + }) + }) + + describe('given a currentUser with a slug', () => { + beforeEach(() => { + currentUser = { slug: 'current-user' } + }) + + describe('backend returns no user for given slug', () => { + beforeEach(() => { + apollo.query.mockResolvedValue({ + data: { User: [] }, + }) + }) + + it('validates', async () => { + await expect(validate({ slug: 'slug' })).resolves.toBeUndefined() + }) + }) + + describe('backend returns user', () => { + let slug + beforeEach(() => { + slug = 'already-taken' + apollo.query.mockResolvedValue({ + data: { User: [{ slug: 'already-taken' }] }, + }) + }) + + it('rejects', async () => { + await expect(validate({ slug: 'uh oh' })).rejects.toThrow('Validation error') + }) + + describe('but it is the current user', () => { + beforeEach(() => { + currentUser = { slug: 'already-taken' } + }) + + it('validates', async () => { + await expect(validate({ slug })).resolves.toBeUndefined() + }) + }) + }) + }) +}) diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index d3c32362f..91204cbef 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -106,7 +106,7 @@ export const followUserMutation = i18n => { return gql` ${userFragment(lang)} mutation($id: ID!) { - follow(id: $id, type: User) { + followUser(id: $id) { name followedByCount followedByCurrentUser @@ -123,7 +123,7 @@ export const unfollowUserMutation = i18n => { return gql` ${userFragment(lang)} mutation($id: ID!) { - unfollow(id: $id, type: User) { + unfollowUser(id: $id) { name followedByCount followedByCurrentUser @@ -145,3 +145,12 @@ export const allowEmbedIframesMutation = () => { } ` } + +export const checkSlugAvailableQuery = gql` + query($slug: String!) { + User(slug: $slug) { + slug + } + } +` + diff --git a/webapp/locales/de.json b/webapp/locales/de.json index d8e380449..8aac8cf85 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -151,11 +151,18 @@ "data": { "name": "Deine Daten", "labelName": "Dein Name", + "labelSlug": "Dein eindeutiger Benutzername", "namePlaceholder": "Petra Lustig", "labelCity": "Deine Stadt oder Region", "labelBio": "Über dich", "success": "Deine Daten wurden erfolgreich aktualisiert!" }, + "validation": { + "slug": { + "regex": "Es sind nur Kleinbuchstaben, Zahlen, Unterstriche oder Bindestriche erlaubt.", + "alreadyTaken": "Dieser Benutzername ist schon vergeben." + } + }, "security": { "name": "Sicherheit", "change-password": { @@ -488,8 +495,7 @@ "happy": "Glücklich", "surprised": "Erstaunt", "cry": "Zum Weinen", - "angry": "Verärgert", - "emoted": "angegeben" + "angry": "Verärgert" }, "category": { "name": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index e97360b55..98b9c244c 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -152,11 +152,18 @@ "data": { "name": "Your data", "labelName": "Your Name", + "labelSlug": "Your unique user name", "namePlaceholder": "Femanon Funny", "labelCity": "Your City or Region", "labelBio": "About You", "success": "Your data was successfully updated!" }, + "validation": { + "slug": { + "regex": "Allowed characters are only lowercase letters, numbers, underscores and hyphens.", + "alreadyTaken": "This user name is already taken." + } + }, "security": { "name": "Security", "change-password": { @@ -489,8 +496,7 @@ "happy": "Happy", "surprised": "Surprised", "cry": "Cry", - "angry": "Angry", - "emoted": "emoted" + "angry": "Angry" }, "category": { "name": { diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index a801d3a34..5e7726d43 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -221,6 +221,7 @@ export default { '@nuxtjs/axios', '@nuxtjs/style-resources', '@nuxtjs/sentry', + '@nuxtjs/pwa', ], /* @@ -294,10 +295,12 @@ export default { }, manifest: { - name: 'Human-Connection.org', - description: 'Human-Connection.org', - theme_color: '#ffffff', - lang: 'de', + name: 'Human Connection', + short_name: 'HC', + homepage_url: 'https://human-connection.org/', + description: 'The free and open source social network for active citizenship', + theme_color: '#17b53f', + lang: 'en', }, /* diff --git a/webapp/package.json b/webapp/package.json index 656d94e80..9d7965785 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -52,15 +52,16 @@ }, "dependencies": { "@human-connection/styleguide": "0.5.21", - "@nuxtjs/apollo": "^4.0.0-rc13", + "@nuxtjs/apollo": "^4.0.0-rc13.1", "@nuxtjs/axios": "~5.6.0", "@nuxtjs/dotenv": "~1.4.1", + "@nuxtjs/pwa": "^3.0.0-beta.19", "@nuxtjs/sentry": "^3.0.0", "@nuxtjs/style-resources": "~1.0.0", "accounting": "~0.4.1", "apollo-cache-inmemory": "~1.6.3", "apollo-client": "~2.6.4", - "cookie-universal-nuxt": "~2.0.17", + "cookie-universal-nuxt": "~2.0.18", "cross-env": "~6.0.0", "date-fns": "2.2.1", "express": "~4.17.1", @@ -77,6 +78,7 @@ "tippy.js": "^4.3.5", "tiptap": "~1.25.0", "tiptap-extensions": "~1.27.0", + "trunc-html": "^1.1.2", "v-tooltip": "~2.0.2", "vue-count-to": "~1.0.13", "vue-infinite-scroll": "^2.0.2", @@ -87,9 +89,9 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@babel/core": "~7.6.0", + "@babel/core": "~7.6.2", "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/preset-env": "~7.6.0", + "@babel/preset-env": "~7.6.2", "@storybook/addon-a11y": "^5.2.1", "@storybook/addon-actions": "^5.2.1", "@storybook/vue": "~5.2.1", @@ -97,6 +99,7 @@ "@vue/eslint-config-prettier": "~5.0.0", "@vue/server-test-utils": "~1.0.0-beta.29", "@vue/test-utils": "~1.0.0-beta.29", + "async-validator": "^3.1.0", "babel-core": "~7.0.0-bridge.0", "babel-eslint": "~10.0.3", "babel-jest": "~24.9.0", diff --git a/webapp/pages/settings/index.spec.js b/webapp/pages/settings/index.spec.js index f0eff0641..1040f2ad0 100644 --- a/webapp/pages/settings/index.spec.js +++ b/webapp/pages/settings/index.spec.js @@ -24,6 +24,7 @@ describe('index.vue', () => { data: { UpdateUser: { id: 'u1', + slug: 'peter', name: 'Peter', locationName: 'Berlin', about: 'Smth', @@ -37,34 +38,67 @@ describe('index.vue', () => { }, } getters = { - 'auth/user': () => { - return {} - }, + 'auth/user': () => ({}), } }) describe('mount', () => { + let options const Wrapper = () => { store = new Vuex.Store({ getters, }) - return mount(index, { store, mocks, localVue }) + return mount(index, { store, mocks, localVue, ...options }) } + beforeEach(() => { + options = {} + }) + it('renders', () => { expect(Wrapper().contains('div')).toBe(true) }) - describe('given a new username and hitting submit', () => { - it('calls updateUser mutation', () => { + describe('given form validation errors', () => { + beforeEach(() => { + options = { + ...options, + computed: { + formSchema: () => ({ + slug: [ + (_rule, _value, callback) => { + callback(new Error('Ouch!')) + }, + ], + }), + }, + } + }) + + it('cannot call updateUser mutation', () => { const wrapper = Wrapper() - const input = wrapper.find('#name') - const submitForm = wrapper.find('.ds-form') - input.setValue('Peter') - submitForm.trigger('submit') + wrapper.find('#name').setValue('Peter') + wrapper.find('.ds-form').trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalled() + expect(mocks.$apollo.mutate).not.toHaveBeenCalled() + }) + }) + + describe('no form validation errors', () => { + beforeEach(() => { + options = { ...options, computed: { formSchema: () => ({}) } } + }) + + describe('given a new username and hitting submit', () => { + it('calls updateUser mutation', () => { + const wrapper = Wrapper() + + wrapper.find('#name').setValue('Peter') + wrapper.find('.ds-form').trigger('submit') + + expect(mocks.$apollo.mutate).toHaveBeenCalled() + }) }) }) }) diff --git a/webapp/pages/settings/index.vue b/webapp/pages/settings/index.vue index 5c99f4b8b..d32d9a91b 100644 --- a/webapp/pages/settings/index.vue +++ b/webapp/pages/settings/index.vue @@ -1,39 +1,49 @@ @@ -42,6 +52,7 @@ import gql from 'graphql-tag' import { mapGetters, mapMutations } from 'vuex' import { CancelToken } from 'axios' +import UniqueSlugForm from '~/components/utils/UniqueSlugForm' let timeout const mapboxToken = process.env.MAPBOX_TOKEN @@ -60,9 +71,10 @@ const query = gql` */ const mutation = gql` - mutation($id: ID!, $name: String, $locationName: String, $about: String) { - UpdateUser(id: $id, name: $name, locationName: $locationName, about: $about) { + mutation($id: ID!, $slug: String, $name: String, $locationName: String, $about: String) { + UpdateUser(id: $id, slug: $slug, name: $name, locationName: $locationName, about: $about) { id + slug name locationName about @@ -84,10 +96,20 @@ export default { ...mapGetters({ currentUser: 'auth/user', }), + formSchema() { + const uniqueSlugForm = UniqueSlugForm({ + apollo: this.$apollo, + currentUser: this.currentUser, + translate: this.$t, + }) + return { + ...uniqueSlugForm.formSchema, + } + }, form: { get: function() { - const { name, locationName, about } = this.currentUser - return { name, locationName, about } + const { name, slug, locationName, about } = this.currentUser + return { name, slug, locationName, about } }, set: function(formData) { this.formData = formData @@ -100,7 +122,7 @@ export default { }), async submit() { this.loadingData = true - const { name, about } = this.formData + const { name, slug, about } = this.formData let { locationName } = this.formData || this.currentUser locationName = locationName && (locationName['label'] || locationName) try { @@ -109,14 +131,16 @@ export default { variables: { id: this.currentUser.id, name, + slug, locationName, about, }, update: (store, { data: { UpdateUser } }) => { - const { name, locationName, about } = UpdateUser + const { name, slug, locationName, about } = UpdateUser this.setCurrentUser({ ...this.currentUser, name, + slug, locationName, about, }) diff --git a/webapp/plugins/vue-filters.js b/webapp/plugins/vue-filters.js index f0b40de93..b13af8fa0 100644 --- a/webapp/plugins/vue-filters.js +++ b/webapp/plugins/vue-filters.js @@ -3,6 +3,7 @@ import Vue from 'vue' import { enUS, de, nl, fr, es } from 'date-fns/locale' import format from 'date-fns/format' import accounting from 'accounting' +import trunc from 'trunc-html' export default ({ app = {} }) => { const locales = { @@ -45,9 +46,9 @@ export default ({ app = {} }) => { if (length <= 0) { return value } - let output = value.substring(0, length) + let output = trunc(value, length).html if (output.length < value.length) { - output += '…' + output += ' …' } return output }, diff --git a/webapp/static/icon.png b/webapp/static/icon.png new file mode 100644 index 000000000..bb9532676 Binary files /dev/null and b/webapp/static/icon.png differ diff --git a/webapp/storybook/helpers.js b/webapp/storybook/helpers.js index 6a479034f..790a10f78 100644 --- a/webapp/storybook/helpers.js +++ b/webapp/storybook/helpers.js @@ -31,7 +31,7 @@ const helpers = { return false }, user(state) { - return { id: 1, name: 'admin' } + return { id: '1', name: 'admin' } }, }, }, @@ -39,7 +39,7 @@ const helpers = { namespaced: true, getters: { placeholder(state) { - return 'Leave your inspirational thoughts...' + return 'Leave your inspirational thoughts ...' }, }, }, diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 57a358db5..979e6952d 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -56,17 +56,17 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@^7.5.5", "@babel/core@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.0.tgz#9b00f73554edd67bebc86df8303ef678be3d7b48" - integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw== +"@babel/core@^7.1.0", "@babel/core@^7.5.5", "@babel/core@~7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91" + integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helpers" "^7.6.0" - "@babel/parser" "^7.6.0" + "@babel/generator" "^7.6.2" + "@babel/helpers" "^7.6.2" + "@babel/parser" "^7.6.2" "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" + "@babel/traverse" "^7.6.2" "@babel/types" "^7.6.0" convert-source-map "^1.1.0" debug "^4.1.0" @@ -87,16 +87,15 @@ source-map "^0.5.0" trim-right "^1.0.1" -"@babel/generator@^7.4.0", "@babel/generator@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56" - integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA== +"@babel/generator@^7.4.0", "@babel/generator@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03" + integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ== dependencies: "@babel/types" "^7.6.0" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" "@babel/helper-annotate-as-pure@^7.0.0": version "7.0.0" @@ -294,13 +293,13 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.0.tgz#21961d16c6a3c3ab597325c34c465c0887d31c6e" - integrity sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ== +"@babel/helpers@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.2.tgz#681ffe489ea4dcc55f23ce469e58e59c1c045153" + integrity sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA== dependencies: "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" + "@babel/traverse" "^7.6.2" "@babel/types" "^7.6.0" "@babel/highlight@^7.0.0": @@ -312,10 +311,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.3", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b" - integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.3", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" + integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -359,10 +358,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-json-strings" "^7.2.0" -"@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58" - integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw== +"@babel/plugin-proposal-object-rest-spread@^7.3.2", "@babel/plugin-proposal-object-rest-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096" + integrity sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" @@ -375,14 +374,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78" - integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA== +"@babel/plugin-proposal-unicode-property-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz#05413762894f41bfe42b9a5e80919bd575dcc802" + integrity sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" "@babel/plugin-syntax-async-generators@^7.2.0": version "7.2.0" @@ -456,10 +455,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.0.tgz#c49e21228c4bbd4068a35667e6d951c75439b1dc" - integrity sha512-tIt4E23+kw6TgL/edACZwP1OUKrjOTyMrFMLoT5IOFrfMRabCgekjqFd5o6PaAMildBu46oFkekIdMuGkkPEpA== +"@babel/plugin-transform-block-scoping@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79" + integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.13" @@ -492,14 +491,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" - integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== +"@babel/plugin-transform-dotall-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz#44abb948b88f0199a627024e1508acaf8dc9b2f9" + integrity sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" "@babel/plugin-transform-duplicate-keys@^7.5.0": version "7.5.0" @@ -581,12 +580,12 @@ "@babel/helper-module-transforms" "^7.1.0" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.0.tgz#1e6e663097813bb4f53d42df0750cf28ad3bb3f1" - integrity sha512-jem7uytlmrRl3iCAuQyw8BpB4c4LWvSpvIeXKpMb+7j84lkx4m4mYr5ErAcmN5KM7B6BqrAvRGjBIbbzqCczew== +"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b" + integrity sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g== dependencies: - regexp-tree "^0.1.13" + regexpu-core "^4.6.0" "@babel/plugin-transform-new-target@^7.4.4": version "7.4.4" @@ -658,10 +657,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-spread@^7.2.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" - integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== +"@babel/plugin-transform-spread@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz#fc77cf798b24b10c46e1b51b1b88c2bf661bb8dd" + integrity sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -688,28 +687,28 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-unicode-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f" - integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA== +"@babel/plugin-transform-unicode-regex@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz#b692aad888a7e8d8b1b214be6b9dc03d5031f698" + integrity sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + regexpu-core "^4.6.0" -"@babel/preset-env@^7.4.5", "@babel/preset-env@^7.5.5", "@babel/preset-env@~7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.0.tgz#aae4141c506100bb2bfaa4ac2a5c12b395619e50" - integrity sha512-1efzxFv/TcPsNXlRhMzRnkBFMeIqBBgzwmZwlFDw5Ubj0AGLeufxugirwZmkkX/ayi3owsSqoQ4fw8LkfK9SYg== +"@babel/preset-env@^7.4.5", "@babel/preset-env@^7.5.5", "@babel/preset-env@~7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3" + integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" "@babel/plugin-proposal-dynamic-import" "^7.5.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.5.5" + "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.6.2" "@babel/plugin-syntax-async-generators" "^7.2.0" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" @@ -718,11 +717,11 @@ "@babel/plugin-transform-arrow-functions" "^7.2.0" "@babel/plugin-transform-async-to-generator" "^7.5.0" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.0" + "@babel/plugin-transform-block-scoping" "^7.6.2" "@babel/plugin-transform-classes" "^7.5.5" "@babel/plugin-transform-computed-properties" "^7.2.0" "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.6.2" "@babel/plugin-transform-duplicate-keys" "^7.5.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" "@babel/plugin-transform-for-of" "^7.4.4" @@ -733,7 +732,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.6.0" "@babel/plugin-transform-modules-systemjs" "^7.5.0" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2" "@babel/plugin-transform-new-target" "^7.4.4" "@babel/plugin-transform-object-super" "^7.5.5" "@babel/plugin-transform-parameters" "^7.4.4" @@ -741,11 +740,11 @@ "@babel/plugin-transform-regenerator" "^7.4.5" "@babel/plugin-transform-reserved-words" "^7.2.0" "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-spread" "^7.6.2" "@babel/plugin-transform-sticky-regex" "^7.2.0" "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.4.4" + "@babel/plugin-transform-unicode-regex" "^7.6.2" "@babel/types" "^7.6.0" browserslist "^4.6.0" core-js-compat "^3.1.1" @@ -798,16 +797,16 @@ "@babel/parser" "^7.6.0" "@babel/types" "^7.6.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516" - integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c" + integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" + "@babel/generator" "^7.6.2" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.0" + "@babel/parser" "^7.6.2" "@babel/types" "^7.6.0" debug "^4.1.0" globals "^11.1.0" @@ -1458,10 +1457,10 @@ webpack-node-externals "^1.7.2" webpackbar "^4.0.0" -"@nuxtjs/apollo@^4.0.0-rc13": - version "4.0.0-rc13" - resolved "https://registry.yarnpkg.com/@nuxtjs/apollo/-/apollo-4.0.0-rc13.tgz#b18a5db3c9f1e5dac4be4848a469b84ae1833b62" - integrity sha512-JoIoexBz0AGkrWl3vkPN7+2hHSY0EJJaI04lfMhc5MQy4PjmYTtpOUFgxLHWSB/HUPeioQwyEdClzPTJrM4yxw== +"@nuxtjs/apollo@^4.0.0-rc13.1": + version "4.0.0-rc13.1" + resolved "https://registry.yarnpkg.com/@nuxtjs/apollo/-/apollo-4.0.0-rc13.1.tgz#35c9ffae5f0a8f94add1bf0480e67a54bd1fca5b" + integrity sha512-a39GSehrJlCB236oEHa3lTwFSHvMWq+0LFosM8UXYMReZPDSNnxtEzfQlRrXsav9iKXcELw31e8sisRB+LcMdQ== dependencies: cross-fetch "^3.0.4" universal-cookie "^4.0.2" @@ -1495,6 +1494,18 @@ consola "^2.5.6" http-proxy-middleware "^0.19.1" +"@nuxtjs/pwa@^3.0.0-beta.19": + version "3.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@nuxtjs/pwa/-/pwa-3.0.0-beta.19.tgz#4685c8137a5b588126b3ee4d469f6806423f958f" + integrity sha512-5c7CB2qrrlpu7BmJeWX9GN//uK1SiEzBbT+ykH11ZfUxQyXiO3QTm1f6tTOnG/P5v4kRIGYdBr0wmRbA/Hv1cw== + dependencies: + defu "^0.0.3" + execa "^1.0.0" + fs-extra "^8.1.0" + hasha "^5.0.0" + jimp-compact "^0.8.0" + workbox-cdn "^4.3.1" + "@nuxtjs/sentry@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@nuxtjs/sentry/-/sentry-3.0.0.tgz#7ca3a25b7b2ea35a953292a7961deb515a6d9140" @@ -2241,12 +2252,7 @@ dependencies: "@types/node" "*" -"@types/cookie@^0.3.1": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.2.tgz#453f4b14b25da6a8ea4494842dedcbf0151deef9" - integrity sha512-aHQA072E10/8iUQsPH7mQU/KUyQBZAGzTVRCUvnSz8mSvbrYsP4xEO2RSA0Pjltolzi0j8+8ixrm//Hr4umPzw== - -"@types/cookie@^0.3.3": +"@types/cookie@^0.3.1", "@types/cookie@^0.3.3": version "0.3.3" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== @@ -3612,6 +3618,16 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +assignment@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/assignment/-/assignment-2.0.0.tgz#ffd17b21bf5d6b22e777b989681a815456a3dd3e" + integrity sha1-/9F7Ib9dayLnd7mJaBqBVFaj3T4= + +assignment@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/assignment/-/assignment-2.2.0.tgz#f5b5bc2d160d69986e8700cd38f567c0aabe101e" + integrity sha1-9bW8LRYNaZhuhwDNOPVnwKq+EB4= + ast-types@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.1.tgz#9461428a270c5a27fda44b738dd3bab2e9353003" @@ -3649,6 +3665,11 @@ async-retry@^1.2.1: dependencies: retry "0.12.0" +async-validator@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.1.0.tgz#447db5eb003cbb47e650f040037a29fc3881ce92" + integrity sha512-XyAHGwtpx3Y3aHIOaGXXFo4tiulnrh+mXBU9INxig6Q8rtmtmBxDuCxb60j7EIGbAsQg9cxfJ2jrUZ+fIqEnBQ== + async@^2.1.4: version "2.6.2" resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" @@ -5279,10 +5300,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie-universal-nuxt@~2.0.17: - version "2.0.17" - resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.0.17.tgz#efa066cade8bc28ab81046c35b6557e3e4ec29fb" - integrity sha512-kJTLOJFOJBiWHd8ehLnheTNyFJbc4zqdZ9YinDSZmWgBMKOrNPd+3hTCsSVGCmybJdpmEJkDenSbRg/xFouqTQ== +cookie-universal-nuxt@~2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.0.18.tgz#95e762a88b5a5b6c23db05521c146260b5576358" + integrity sha512-+5ciWAm1B15JN5e4LVnU4Ovs9KqBeYFYwaHrm9ThDZr/12u9REJfxH3wji0iY9NnF2ard3ULlD+R4uEQ0vUNKg== dependencies: "@types/cookie" "^0.3.1" cookie-universal "^2.0.16" @@ -5937,6 +5958,11 @@ defu@^0.0.1: resolved "https://registry.yarnpkg.com/defu/-/defu-0.0.1.tgz#74dc4d64e401d7f95c6755fe98bc5cd688833a8f" integrity sha512-Pz9yznbSzVTNA67lcfqVnktROx2BrrBBcmQqGrfe0zdiN5pl5GQogLA4uaP3U1pR1LHIZpEYTAh2sn+v4rH1dA== +defu@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/defu/-/defu-0.0.3.tgz#bdc3ea1e1ab2120d4d4a129147f3ba9b7f9fe103" + integrity sha512-u/fe4fBwrD0KACvI0sYWTWFzooqONZq8ywPnK0ZkAgLNwaDTKpSWvMiiU4QmzhrQCXu8Y0+HIWP8amE18lsL4A== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -7906,6 +7932,14 @@ hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.1.0.tgz#dd05ccdfcfe7dab626247ce2a58efe461922f4ca" + integrity sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hast-util-parse-selector@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.2.tgz#66aabccb252c47d94975f50a281446955160380b" @@ -7921,6 +7955,11 @@ hastscript@^5.0.0: property-information "^5.0.1" space-separated-tokens "^1.0.0" +he@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/he/-/he-0.5.0.tgz#2c05ffaef90b68e860f3fd2b54ef580989277ee2" + integrity sha1-LAX/rvkLaOhg8/0rVO9YCYknfuI= + he@1.2.x, he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -8402,6 +8441,14 @@ inquirer@^6.2.2: strip-ansi "^5.1.0" through "^2.3.6" +insane@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/insane/-/insane-2.6.1.tgz#c7dcae7b51c20346883b71078fad6ce0483c198f" + integrity sha1-x9yue1HCA0aIO3EHj61s4Eg8GY8= + dependencies: + assignment "2.0.0" + he "0.5.0" + interpret@^1.0.0, interpret@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -9304,6 +9351,11 @@ jest@~24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +jimp-compact@^0.8.0: + version "0.8.4" + resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.8.4.tgz#0878a0c30f22d2d4f8b33e96722eb09d20770627" + integrity sha512-9mvZ7/TJ28bWtdx0RxmfiOTzSom4zuRniFTLtJHfNL6HxQdnRtjmX8XIRjmofgVXj2TW/GgSuZKB3dSZ5hNhKg== + js-base64@^2.1.8: version "2.5.1" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" @@ -12991,10 +13043,10 @@ refractor@^2.4.1: parse-entities "^1.1.2" prismjs "~1.17.0" -regenerate-unicode-properties@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz#7b38faa296252376d363558cfbda90c9ce709662" - integrity sha512-SbA/iNrBUf6Pv2zU8Ekv1Qbhv92yxL4hiDa2siuxs4KKn4oOoMDHXjAf7+Nz9qinUQ46B1LcWEi/PhJfPWpZWQ== +regenerate-unicode-properties@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" + integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== dependencies: regenerate "^1.4.0" @@ -13038,11 +13090,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp-tree@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f" - integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw== - regexp.prototype.flags@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c" @@ -13060,13 +13107,13 @@ regexpp@^3.0.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e" integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g== -regexpu-core@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" - integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== +regexpu-core@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" + integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== dependencies: regenerate "^1.4.0" - regenerate-unicode-properties "^8.0.2" + regenerate-unicode-properties "^8.1.0" regjsgen "^0.5.0" regjsparser "^0.6.0" unicode-match-property-ecmascript "^1.0.4" @@ -14659,6 +14706,20 @@ trim-right@^1.0.1: dependencies: glob "^7.1.2" +trunc-html@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/trunc-html/-/trunc-html-1.1.2.tgz#1e97d51f67d470b67662b1a670e6d0ea7a8edafe" + integrity sha1-HpfVH2fUcLZ2YrGmcObQ6nqO2v4= + dependencies: + assignment "2.2.0" + insane "2.6.1" + trunc-text "1.0.1" + +trunc-text@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5" + integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU= + tryer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -14748,6 +14809,11 @@ type-fest@^0.5.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== +type-fest@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.0.tgz#ee92ee2ec95479869dec66d17d9698666b90f29d" + integrity sha512-M8BLNtxNWRbRmJ8Iu+4j4qZLlE7Y75ldC42cvw9KPOFkFwY/KlSJuj9eeGmoB/k3QAAnuN3M35Z59+lBm1+C+g== + type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -15594,6 +15660,11 @@ wordwrap@~1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +workbox-cdn@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-cdn/-/workbox-cdn-4.3.1.tgz#f1ffed5368c20291048498ba0744baf27dbd7294" + integrity sha512-Adkgo+/7S+bBsDTzdeH0xxQCrfBM1EiyZlvu1tMh0cJ/ipC6TtA8KDr12PBREdbL0zO9hG+7OSzvi2NLchPAEg== + worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" diff --git a/yarn.lock b/yarn.lock index e824a6944..0380317ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1573,10 +1573,10 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codecov@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.5.0.tgz#3d0748932f9cb41e1ad7f21fa346ef1b2b1bed47" - integrity sha512-/OsWOfIHaQIr7aeZ4pY0UC1PZT6kimoKFOFYFNb6wxo3iw12nRrh+mNGH72rnXxNsq6SGfesVPizm/6Q3XqcFQ== +codecov@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.6.1.tgz#f39fc49413445555f81f8e3ca5730992843b4517" + integrity sha512-IUJB6WG47nWK7o50etF8jBadxdMw7DmoQg05yIljstXFBGB6clOZsIj6iD4P82T2YaIU3qq+FFu8K9pxgkCJDQ== dependencies: argv "^0.0.2" ignore-walk "^3.0.1"