Merge branch 'master' into Allow_embedded_code_in_posts_permanent_memory

This commit is contained in:
Alexander Friedland 2019-09-25 19:11:05 +02:00 committed by GitHub
commit deb21f6edb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1154 additions and 799 deletions

View File

@ -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",

View File

@ -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,

View File

@ -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] },

View File

@ -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/,
)
})
})
})

View File

@ -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()
},
},
}

View File

@ -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,
})
})
})
})

View File

@ -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)
})
})
})
})

View File

@ -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', () => {

View File

@ -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!

View File

@ -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

View File

@ -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"

View File

@ -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
})

View File

@ -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",

View File

@ -3,3 +3,4 @@ build
.nuxt
styleguide/
**/*.min.js
static/sw.js

2
webapp/.gitignore vendored
View File

@ -84,3 +84,5 @@ cypress.env.json
# Apple macOS folder attribute file
.DS_Store
sw.*

View File

@ -30,6 +30,7 @@ describe('Comment.vue', () => {
},
$filters: {
truncate: a => a,
removeHtml: a => a,
},
$apollo: {
mutate: jest.fn().mockResolvedValue({

View File

@ -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:
'<p></p><p>Thank you all!</p><p><a href="/profile/5ab6a050896325000a59e514" target="_blank">@wolfgang-huss</a> <a href="/profile/5b1693daf850c11207fa6109" target="_blank">@robert-schafer</a> <a href="/profile/5a663b1ac64291000bf302a1" target="_blank">@greg</a> <a href="/profile/5acc8a337c6b11000b3c7612" target="_blank">@human-connection</a></p><p>watch my video</p><p><a href="https://www.youtube.com/watch?v=EeVspZ8jC6I" class="embed" target="_blank"></a></p><p></p><p>I think we can all learn a lot from Alex\'s video :) </p><p>It\'s really great stuff!!</p><p>Please give him a big smiley face emoticon :D</p>',
contentExcerpt:
'<p></p><p>Thank you all!</p><p><a href="/profile/5ab6a050896325000a59e514" target="_blank">@wolfgang-huss</a> <a href="/profile/5b1693daf850c11207fa6109" target="_blank">@robert-schafer</a> <a href="/profile/5a663b1ac64291000bf302a1" target="_blank">@greg</a> <a href="/profile/5acc8a337c6b11000b3c7612" target="_blank">@human-connection</a></p><p>watch my video</p><p><a href="https://www.youtube.com/watch?v=EeVspZ8jC6I" target="_blank"></a></p><p></p><p>I think we can all learn a lot from Alex\'s video :) </p><p>It\'s really great stuff!!</p><p>Please give him a …</p>',
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: `<comment :key="comment.id" :comment="comment" />`,
}))

View File

@ -11,7 +11,7 @@
</div>
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
<ds-card :id="`commentId-${comment.id}`">
<ds-space margin-bottom="small">
<ds-space margin-bottom="small" margin-top="base">
<hc-user :user="author" :date-time="comment.createdAt" />
<!-- Content Menu (can open Modals) -->
<client-only>
@ -21,14 +21,12 @@
resource-type="comment"
:resource="comment"
:modalsData="menuModalsData"
style="float-right"
class="float-right"
:is-owner="isAuthor(author.id)"
@showEditCommentMenu="editCommentMenu"
/>
</client-only>
</ds-space>
<ds-space margin-bottom="small" />
<div v-if="openEditCommentMenu">
<hc-comment-form
:update="true"
@ -36,21 +34,19 @@
:comment="comment"
@showEditCommentMenu="editCommentMenu"
@updateComment="updateComment"
@collapse="isCollapsed = true"
/>
</div>
<div v-show="!openEditCommentMenu">
<content-viewer
v-if="comment.content.length < 180"
v-if="$filters.removeHtml(comment.content).length < 180"
:content="comment.content"
class="padding-left"
/>
<div
v-show="comment.content !== comment.contentExcerpt && comment.content.length > 180"
class="show-more-or-less-div"
>
<div v-else class="show-more-or-less-div">
<content-viewer
v-if="isCollapsed"
:content="comment.contentExcerpt"
:content="$filters.truncate(comment.content, 180)"
class="padding-left text-align-left"
/>
<span class="show-more-or-less">
@ -59,10 +55,14 @@
</a>
</span>
</div>
<content-viewer v-if="!isCollapsed" :content="comment.content" class="padding-left" />
<content-viewer
v-if="!isCollapsed"
:content="comment.content"
class="padding-left text-align-left"
/>
<div class="show-more-or-less-div">
<span class="show-more-or-less">
<a v-if="!isCollapsed" @click="isCollapsed = !isCollapsed" class="padding-left">
<a v-if="!isCollapsed" class="padding-left" @click="isCollapsed = !isCollapsed">
{{ $t('comment.show.less') }}
</a>
</span>
@ -169,6 +169,10 @@ export default {
}
</script>
<style lang="scss" scoped>
.float-right {
float: right;
}
.padding-left {
padding-left: 40px;
}
@ -180,12 +184,11 @@ export default {
div.show-more-or-less-div {
text-align: right;
margin-right: 20px;
margin-top: -12px;
}
span.show-more-or-less {
display: block;
margin: 10px 20px;
margin: 0px 20px;
cursor: pointer;
}
</style>

View File

@ -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()

View File

@ -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')
})

View File

@ -12,7 +12,7 @@
>
{{ post.comments.length }}
</ds-tag>
&nbsp; Comments
<span class="list-title">{{ $t('common.comment', null, 0) }}</span>
</span>
</h3>
<ds-space margin-bottom="large" />
@ -26,17 +26,14 @@
@updateComment="updateCommentList"
/>
</div>
<hc-empty v-else name="empty" icon="messages" />
</div>
</template>
<script>
import Comment from '~/components/Comment.vue'
import HcEmpty from '~/components/Empty.vue'
import Comment from '~/components/Comment/Comment'
export default {
components: {
Comment,
HcEmpty,
},
props: {
post: { type: Object, default: () => {} },
@ -50,3 +47,9 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.list-title {
margin-left: $space-x-small;
}
</style>

View File

@ -9,7 +9,6 @@
<p style="display: inline" :key="PostsEmotionsCountByEmotion[emotion]">
{{ PostsEmotionsCountByEmotion[emotion] }}x
</p>
{{ $t('contribution.emotions-label.emoted') }}
</div>
</div>
</template>

View File

@ -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

View File

@ -8,80 +8,90 @@
</ds-text>
</ds-space>
</ds-card>
<ds-form
v-else
class="create-user-account"
v-model="formData"
:schema="formSchema"
@submit="submit"
>
<template slot-scope="{ errors }">
<ds-card class="create-account-card" :header="$t('registration.create-user-account.title')">
<client-only>
<locale-switch class="create-account-locale-switch" offset="5" />
</client-only>
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
<ds-input
id="about"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<ds-card v-else class="create-account-card">
<client-only>
<locale-switch />
</client-only>
<ds-space centered>
<img
class="create-account-image"
alt="Create an account for Human Connection"
src="/img/sign-up/nicetomeetyou.svg"
/>
</ds-space>
<ds-space>
<ds-heading size="h3">
{{ $t('registration.create-user-account.title') }}
</ds-heading>
</ds-space>
<ds-text>
<input
id="checkbox"
type="checkbox"
v-model="termsAndConditionsConfirmed"
:checked="termsAndConditionsConfirmed"
/>
<label
for="checkbox"
v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
></label>
</ds-text>
<ds-form class="create-user-account" v-model="formData" :schema="formSchema" @submit="submit">
<template v-slot="{ errors }">
<ds-flex gutter="base">
<ds-flex-item width="100%">
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
<ds-input
id="about"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<template slot="footer">
<ds-space class="backendErrors" v-if="backendErrors">
<ds-text align="center" bold color="danger">{{ backendErrors.message }}</ds-text>
</ds-space>
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="$apollo.loading"
:disabled="errors || !termsAndConditionsConfirmed"
primary
>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
</template>
</ds-form>
<ds-text>
<input
id="checkbox"
type="checkbox"
v-model="termsAndConditionsConfirmed"
:checked="termsAndConditionsConfirmed"
/>
<label
for="checkbox"
v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
></label>
</ds-text>
</ds-flex-item>
<ds-flex-item width="100%">
<ds-space class="backendErrors" v-if="backendErrors">
<ds-text align="center" bold color="danger">{{ backendErrors.message }}</ds-text>
</ds-space>
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="$apollo.loading"
:disabled="errors || !termsAndConditionsConfirmed"
primary
>
{{ $t('actions.save') }}
</ds-button>
</ds-flex-item>
</ds-flex>
</template>
</ds-form>
</ds-card>
</ds-container>
</template>
@ -157,13 +167,9 @@ export default {
}
</script>
<style lang="scss">
.create-account-card {
position: relative;
}
.create-account-locale-switch {
position: absolute;
top: 1em;
right: 1em;
<style lang="scss" scoped>
.create-account-image {
width: 50%;
max-width: 200px;
}
</style>

View File

@ -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)()
},
},
],
},
}
}

View File

@ -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()
})
})
})
})
})

View File

@ -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
}
}
`

View File

@ -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": {

View File

@ -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": {

View File

@ -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',
},
/*

View File

@ -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",

View File

@ -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()
})
})
})
})

View File

@ -1,39 +1,49 @@
<template>
<ds-form v-model="form" @submit="submit">
<ds-card :header="$t('settings.data.name')">
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
<!-- eslint-disable vue/use-v-on-exact -->
<ds-select
id="city"
model="locationName"
icon="map-marker"
:options="cities"
:label="$t('settings.data.labelCity')"
:placeholder="$t('settings.data.labelCity')"
:loading="loadingGeo"
@input.native="handleCityInput"
/>
<!-- eslint-enable vue/use-v-on-exact -->
<ds-input
id="bio"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<template slot="footer">
<ds-button style="float: right;" icon="check" type="submit" :loading="loadingData" primary>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
<ds-form v-model="form" :schema="formSchema" @submit="submit">
<template slot-scope="{ errors }">
<ds-card :header="$t('settings.data.name')">
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
<ds-input id="slug" model="slug" icon="at" :label="$t('settings.data.labelSlug')" />
<!-- eslint-disable vue/use-v-on-exact -->
<ds-select
id="city"
model="locationName"
icon="map-marker"
:options="cities"
:label="$t('settings.data.labelCity')"
:placeholder="$t('settings.data.labelCity')"
:loading="loadingGeo"
@input.native="handleCityInput"
/>
<!-- eslint-enable vue/use-v-on-exact -->
<ds-input
id="bio"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<template slot="footer">
<ds-button
style="float: right;"
icon="check"
:disabled="errors"
type="submit"
:loading="loadingData"
primary
>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-card>
</template>
</ds-form>
</template>
@ -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,
})

View File

@ -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
},

BIN
webapp/static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -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 ...'
},
},
},

View File

@ -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"

View File

@ -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"