diff --git a/.travis.yml b/.travis.yml
index 42b427a11..f48b0bb36 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,12 +21,15 @@ install:
- wait-on http://localhost:7474
script:
+ - export CYPRESS_RETRIES=1
+ - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
+ - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"
# Backend
- docker-compose exec backend yarn run lint
- docker-compose exec backend yarn run test:jest --ci --verbose=false --coverage
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
- - docker-compose exec backend yarn run test:cucumber
+ - docker-compose exec backend yarn run test:cucumber --tags "not @wip"
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
# Frontend
@@ -34,7 +37,7 @@ script:
- docker-compose exec webapp yarn run test --ci --verbose=false --coverage
- docker-compose exec -d backend yarn run test:before:seeder
# Fullstack
- - CYPRESS_RETRIES=1 yarn run cypress:run
+ - yarn run cypress:run
# Coverage
- codecov
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 908252f41..e2a727871 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,4 +9,4 @@
],
"editor.formatOnSave": true,
"eslint.autoFixOnSave": true
-}
\ No newline at end of file
+}
diff --git a/backend/Dockerfile b/backend/Dockerfile
index d24f2747e..f0251bddc 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -24,4 +24,5 @@ RUN yarn run build
FROM base as production
ENV NODE_ENV=production
COPY --from=builder /nitro-backend/dist ./dist
+COPY ./public/img/ ./public/img/
RUN yarn install --frozen-lockfile --non-interactive
diff --git a/backend/package.json b/backend/package.json
index 40ea9476d..e6c6f7cc3 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -44,15 +44,15 @@
"dependencies": {
"activitystrea.ms": "~2.1.3",
"apollo-cache-inmemory": "~1.6.2",
- "apollo-client": "~2.6.2",
- "apollo-link-context": "~1.0.14",
- "apollo-link-http": "~1.5.14",
- "apollo-server": "~2.6.2",
+ "apollo-client": "~2.6.3",
+ "apollo-link-context": "~1.0.18",
+ "apollo-link-http": "~1.5.15",
+ "apollo-server": "~2.6.3",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~5.2.0",
- "date-fns": "2.0.0-alpha.31",
+ "date-fns": "2.0.0-alpha.34",
"debug": "~4.1.1",
"dotenv": "~8.0.0",
"express": "~4.17.1",
@@ -61,7 +61,7 @@
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
"graphql-middleware": "~3.0.2",
- "graphql-shield": "~5.3.6",
+ "graphql-shield": "~5.3.8",
"graphql-tag": "~2.10.1",
"graphql-yoga": "~1.17.4",
"helmet": "~3.18.0",
@@ -87,14 +87,14 @@
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.4.5",
"@babel/register": "~7.4.4",
- "apollo-server-testing": "~2.6.2",
+ "apollo-server-testing": "~2.6.3",
"babel-core": "~7.0.0-0",
- "babel-eslint": "~10.0.1",
+ "babel-eslint": "~10.0.2",
"babel-jest": "~24.8.0",
"chai": "~4.2.0",
"cucumber": "~5.1.0",
"eslint": "~5.16.0",
- "eslint-config-prettier": "~4.3.0",
+ "eslint-config-prettier": "~5.0.0",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.17.3",
"eslint-plugin-jest": "~22.6.4",
@@ -105,7 +105,7 @@
"graphql-request": "~1.8.2",
"jest": "~24.8.0",
"nodemon": "~1.19.1",
- "prettier": "~1.17.1",
+ "prettier": "~1.18.2",
"supertest": "~4.0.2"
}
}
diff --git a/backend/src/activitypub/NitroDataSource.js b/backend/src/activitypub/NitroDataSource.js
index eea37337a..0900bed6c 100644
--- a/backend/src/activitypub/NitroDataSource.js
+++ b/backend/src/activitypub/NitroDataSource.js
@@ -505,9 +505,7 @@ export default class NitroDataSource {
const result2 = await this.client.mutate({
mutation: gql`
mutation {
- AddCommentAuthor(from: {id: "${
- result.data.CreateComment.id
- }"}, to: {id: "${toUserId}"}) {
+ AddCommentAuthor(from: {id: "${result.data.CreateComment.id}"}, to: {id: "${toUserId}"}) {
id
}
}
@@ -519,9 +517,7 @@ export default class NitroDataSource {
result = await this.client.mutate({
mutation: gql`
mutation {
- AddCommentPost(from: { id: "${
- result.data.CreateComment.id
- }", to: { id: "${postId}" }}) {
+ AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) {
id
}
}
diff --git a/backend/src/middleware/fixImageUrlsMiddleware.js b/backend/src/middleware/fixImageUrlsMiddleware.js
index c930915bf..3bfa8537a 100644
--- a/backend/src/middleware/fixImageUrlsMiddleware.js
+++ b/backend/src/middleware/fixImageUrlsMiddleware.js
@@ -6,8 +6,11 @@ const legacyUrls = [
export const fixUrl = url => {
legacyUrls.forEach(legacyUrl => {
- url = url.replace(legacyUrl, '/api')
+ url = url.replace(legacyUrl, '')
})
+ if (!url.startsWith('/')) {
+ url = `/${url}`
+ }
return url
}
diff --git a/backend/src/middleware/fixImageUrlsMiddleware.spec.js b/backend/src/middleware/fixImageUrlsMiddleware.spec.js
index b2d808dd9..0da66811a 100644
--- a/backend/src/middleware/fixImageUrlsMiddleware.spec.js
+++ b/backend/src/middleware/fixImageUrlsMiddleware.spec.js
@@ -1,12 +1,19 @@
import { fixImageURLs } from './fixImageUrlsMiddleware'
describe('fixImageURLs', () => {
+ describe('edge case: image url is exact match of legacy url', () => {
+ it('replaces it with `/`', () => {
+ const url = 'https://api-alpha.human-connection.org'
+ expect(fixImageURLs(url)).toEqual('/')
+ })
+ })
+
describe('image url of legacy alpha', () => {
it('removes domain', () => {
const url =
'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png'
expect(fixImageURLs(url)).toEqual(
- '/api/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
+ '/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
)
})
})
@@ -16,7 +23,7 @@ describe('fixImageURLs', () => {
const url =
'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg'
expect(fixImageURLs(url)).toEqual(
- '/api/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
+ '/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
)
})
})
diff --git a/backend/src/middleware/notifications/spec.js b/backend/src/middleware/notifications/spec.js
index 65212e544..985654b0f 100644
--- a/backend/src/middleware/notifications/spec.js
+++ b/backend/src/middleware/notifications/spec.js
@@ -87,9 +87,7 @@ describe('currentUser { notifications }', () => {
describe('who mentions me again', () => {
beforeEach(async () => {
- const updatedContent = `${
- post.content
- } One more mention to @al-capone`
+ const updatedContent = `${post.content} One more mention to @al-capone`
// The response `post.content` contains a link but the XSSmiddleware
// should have the `mention` CSS class removed. I discovered this
// during development and thought: A feature not a bug! This way we
diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js
index bc9b4c525..10b777748 100644
--- a/backend/src/middleware/permissionsMiddleware.js
+++ b/backend/src/middleware/permissionsMiddleware.js
@@ -1,4 +1,4 @@
-import { rule, shield, allow, or } from 'graphql-shield'
+import { rule, shield, deny, allow, or } from 'graphql-shield'
/*
* TODO: implement
@@ -16,6 +16,12 @@ const isAdmin = rule()(async (parent, args, { user }, info) => {
return user && user.role === 'admin'
})
+const onlyYourself = rule({
+ cache: 'no_cache',
+})(async (parent, args, context, info) => {
+ return context.user.id === args.id
+})
+
const isMyOwn = rule({
cache: 'no_cache',
})(async (parent, args, context, info) => {
@@ -48,6 +54,13 @@ const belongsToMe = rule({
return Boolean(notification)
})
+/* TODO: decide if we want to remove this check: the check
+ * `onlyEnabledContent` throws authorization errors only if you have
+ * arguments for `disabled` or `deleted` assuming these are filter
+ * parameters. Soft-delete middleware obfuscates data on its way out
+ * anyways. Furthermore, `neo4j-graphql-js` offers many ways to filter for
+ * data so I believe, this is not a good check anyways.
+ */
const onlyEnabledContent = rule({
cache: 'strict',
})(async (parent, args, ctx, info) => {
@@ -80,47 +93,70 @@ const isAuthor = rule({
return authorId === user.id
})
-// Permissions
-const permissions = shield({
- Query: {
- Notification: isAdmin,
- statistics: allow,
- currentUser: allow,
- Post: or(onlyEnabledContent, isModerator),
- },
- Mutation: {
- UpdateNotification: belongsToMe,
- CreatePost: isAuthenticated,
- UpdatePost: isAuthor,
- DeletePost: isAuthor,
- report: isAuthenticated,
- CreateBadge: isAdmin,
- UpdateBadge: isAdmin,
- DeleteBadge: isAdmin,
- AddUserBadges: isAdmin,
- CreateSocialMedia: isAuthenticated,
- DeleteSocialMedia: isAuthenticated,
- // AddBadgeRewarded: isAdmin,
- // RemoveBadgeRewarded: isAdmin,
- reward: isAdmin,
- unreward: isAdmin,
- // addFruitToBasket: isAuthenticated
- follow: isAuthenticated,
- unfollow: isAuthenticated,
- shout: isAuthenticated,
- unshout: isAuthenticated,
- changePassword: isAuthenticated,
- enable: isModerator,
- disable: isModerator,
- CreateComment: isAuthenticated,
- DeleteComment: isAuthor,
- // CreateUser: allow,
- },
- User: {
- email: isMyOwn,
- password: isMyOwn,
- privateKey: isMyOwn,
- },
+const isDeletingOwnAccount = rule({
+ cache: 'no_cache',
+})(async (parent, args, context, info) => {
+ return context.user.id === args.id
})
+// Permissions
+const permissions = shield(
+ {
+ Query: {
+ '*': deny,
+ findPosts: allow,
+ Category: isAdmin,
+ Tag: isAdmin,
+ Report: isModerator,
+ Notification: isAdmin,
+ statistics: allow,
+ currentUser: allow,
+ Post: or(onlyEnabledContent, isModerator),
+ Comment: allow,
+ User: allow,
+ isLoggedIn: allow,
+ },
+ Mutation: {
+ '*': deny,
+ login: allow,
+ UpdateNotification: belongsToMe,
+ CreateUser: isAdmin,
+ UpdateUser: onlyYourself,
+ CreatePost: isAuthenticated,
+ UpdatePost: isAuthor,
+ DeletePost: isAuthor,
+ report: isAuthenticated,
+ CreateBadge: isAdmin,
+ UpdateBadge: isAdmin,
+ DeleteBadge: isAdmin,
+ AddUserBadges: isAdmin,
+ CreateSocialMedia: isAuthenticated,
+ DeleteSocialMedia: isAuthenticated,
+ // AddBadgeRewarded: isAdmin,
+ // RemoveBadgeRewarded: isAdmin,
+ reward: isAdmin,
+ unreward: isAdmin,
+ // addFruitToBasket: isAuthenticated
+ follow: isAuthenticated,
+ unfollow: isAuthenticated,
+ shout: isAuthenticated,
+ unshout: isAuthenticated,
+ changePassword: isAuthenticated,
+ enable: isModerator,
+ disable: isModerator,
+ CreateComment: isAuthenticated,
+ DeleteComment: isAuthor,
+ DeleteUser: isDeletingOwnAccount,
+ },
+ User: {
+ email: isMyOwn,
+ password: isMyOwn,
+ privateKey: isMyOwn,
+ },
+ },
+ {
+ fallbackRule: allow,
+ },
+)
+
export default permissions
diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js
index 79bba0a5d..4e060dc90 100644
--- a/backend/src/middleware/slugifyMiddleware.spec.js
+++ b/backend/src/middleware/slugifyMiddleware.spec.js
@@ -7,12 +7,14 @@ let headers
const factory = Factory()
beforeEach(async () => {
- await factory.create('User', { email: 'user@example.org', password: '1234' })
+ const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' }
+ await factory.create('User', adminParams)
await factory.create('User', {
email: 'someone@example.org',
password: '1234',
})
- headers = await login({ email: 'user@example.org', password: '1234' })
+ // we need to be an admin, otherwise we're not authorized to create a user
+ headers = await login(adminParams)
authenticatedClient = new GraphQLClient(host, { headers })
})
diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js
index 9e2ec70a2..3bff53ddb 100644
--- a/backend/src/schema/resolvers/posts.spec.js
+++ b/backend/src/schema/resolvers/posts.spec.js
@@ -74,6 +74,22 @@ describe('CreatePost', () => {
await expect(client.request(mutation)).resolves.toMatchObject(expected)
})
})
+
+ describe('language', () => {
+ it('allows a user to set the language of the post', async () => {
+ const createPostWithLanguageMutation = `
+ mutation {
+ CreatePost(title: "I am a title", content: "Some content", language: "en") {
+ language
+ }
+ }
+ `
+ const expected = { CreatePost: { language: 'en' } }
+ await expect(client.request(createPostWithLanguageMutation)).resolves.toEqual(
+ expect.objectContaining(expected),
+ )
+ })
+ })
})
})
diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js
index cf648a6bd..463c5ea6d 100644
--- a/backend/src/schema/resolvers/user_management.spec.js
+++ b/backend/src/schema/resolvers/user_management.spec.js
@@ -315,6 +315,8 @@ describe('change password', () => {
describe('do not expose private RSA key', () => {
let headers
let client
+ let authenticatedClient
+
const queryUserPuplicKey = gql`
query($queriedUserSlug: String) {
User(slug: $queriedUserSlug) {
@@ -332,7 +334,7 @@ describe('do not expose private RSA key', () => {
}
`
- const actionGenUserWithKeys = async () => {
+ const generateUserWithKeys = async authenticatedClient => {
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
const variables = {
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
@@ -341,7 +343,7 @@ describe('do not expose private RSA key', () => {
name: 'Apfel Strudel',
email: 'apfel-strudel@test.org',
}
- await client.request(
+ await authenticatedClient.request(
gql`
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String!) {
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
@@ -353,14 +355,23 @@ describe('do not expose private RSA key', () => {
)
}
- // not authenticate
beforeEach(async () => {
+ const adminParams = {
+ role: 'admin',
+ email: 'admin@example.org',
+ password: '1234',
+ }
+ // create an admin user who has enough permissions to create other users
+ await factory.create('User', adminParams)
+ const headers = await login(adminParams)
+ authenticatedClient = new GraphQLClient(host, { headers })
+ // but also create an unauthenticated client to issue the `User` query
client = new GraphQLClient(host)
})
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
it('returns publicKey', async () => {
- await actionGenUserWithKeys()
+ await generateUserWithKeys(authenticatedClient)
await expect(
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
).toEqual(
@@ -378,7 +389,7 @@ describe('do not expose private RSA key', () => {
describe('unauthenticated query of "privateKey"', () => {
it('throws "Not Authorised!"', async () => {
- await actionGenUserWithKeys()
+ await generateUserWithKeys(authenticatedClient)
await expect(
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
).rejects.toThrow('Not Authorised')
@@ -393,7 +404,7 @@ describe('do not expose private RSA key', () => {
describe('authenticated query of "publicKey"', () => {
it('returns publicKey', async () => {
- await actionGenUserWithKeys()
+ await generateUserWithKeys(authenticatedClient)
await expect(
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' }),
).toEqual(
@@ -411,7 +422,7 @@ describe('do not expose private RSA key', () => {
describe('authenticated query of "privateKey"', () => {
it('throws "Not Authorised!"', async () => {
- await actionGenUserWithKeys()
+ await generateUserWithKeys(authenticatedClient)
await expect(
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' }),
).rejects.toThrow('Not Authorised')
diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js
index 53bf0967e..c5c3701b5 100644
--- a/backend/src/schema/resolvers/users.js
+++ b/backend/src/schema/resolvers/users.js
@@ -11,5 +11,27 @@ export default {
params = await fileUpload(params, { file: 'avatarUpload', url: 'avatar' })
return neo4jgraphql(object, params, context, resolveInfo, false)
},
+ DeleteUser: async (object, params, context, resolveInfo) => {
+ const { resource } = params
+ const session = context.driver.session()
+
+ if (resource && resource.length) {
+ await Promise.all(
+ resource.map(async node => {
+ await session.run(
+ `
+ MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId})
+ SET resource.deleted = true
+ RETURN author`,
+ {
+ userId: context.user.id,
+ },
+ )
+ }),
+ )
+ session.close()
+ }
+ return neo4jgraphql(object, params, context, resolveInfo, false)
+ },
},
}
diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js
index a5c50f4f9..352d38eaa 100644
--- a/backend/src/schema/resolvers/users.spec.js
+++ b/backend/src/schema/resolvers/users.spec.js
@@ -1,6 +1,7 @@
import { GraphQLClient } from 'graphql-request'
-import { host } from '../../jest/helpers'
+import { login, host } from '../../jest/helpers'
import Factory from '../../seed/factories'
+import gql from 'graphql-tag'
const factory = Factory()
let client
@@ -18,27 +19,58 @@ describe('users', () => {
}
}
`
- client = new GraphQLClient(host)
-
- it('with password and email', async () => {
+ describe('given valid password and email', () => {
const variables = {
name: 'John Doe',
password: '123',
email: '123@123.de',
}
- const expected = {
- CreateUser: {
- id: expect.any(String),
- },
- }
- await expect(client.request(mutation, variables)).resolves.toEqual(expected)
+
+ describe('unauthenticated', () => {
+ beforeEach(async () => {
+ client = new GraphQLClient(host)
+ })
+
+ it('is not allowed to create users', async () => {
+ await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
+ })
+ })
+
+ describe('authenticated admin', () => {
+ beforeEach(async () => {
+ const adminParams = {
+ role: 'admin',
+ email: 'admin@example.org',
+ password: '1234',
+ }
+ await factory.create('User', adminParams)
+ const headers = await login(adminParams)
+ client = new GraphQLClient(host, { headers })
+ })
+
+ it('is allowed to create new users', async () => {
+ const expected = {
+ CreateUser: {
+ id: expect.any(String),
+ },
+ }
+ await expect(client.request(mutation, variables)).resolves.toEqual(expected)
+ })
+ })
})
})
describe('UpdateUser', () => {
- beforeEach(async () => {
- await factory.create('User', { id: 'u47', name: 'John Doe' })
- })
+ const userParams = {
+ email: 'user@example.org',
+ password: '1234',
+ id: 'u47',
+ name: 'John Doe',
+ }
+ const variables = {
+ id: 'u47',
+ name: 'John Doughnut',
+ }
const mutation = `
mutation($id: ID!, $name: String) {
@@ -48,38 +80,199 @@ describe('users', () => {
}
}
`
- client = new GraphQLClient(host)
- it('name within specifications', async () => {
- const variables = {
- id: 'u47',
- name: 'James Doe',
- }
- const expected = {
- UpdateUser: {
- id: 'u47',
+ beforeEach(async () => {
+ await factory.create('User', userParams)
+ })
+
+ describe('as another user', () => {
+ beforeEach(async () => {
+ const someoneElseParams = {
+ email: 'someoneElse@example.org',
+ password: '1234',
name: 'James Doe',
- },
- }
- await expect(client.request(mutation, variables)).resolves.toEqual(expected)
+ }
+
+ await factory.create('User', someoneElseParams)
+ const headers = await login(someoneElseParams)
+ client = new GraphQLClient(host, { headers })
+ })
+
+ it('is not allowed to change other user accounts', async () => {
+ await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
+ })
})
- it('with no name', async () => {
- const variables = {
- id: 'u47',
- name: null,
+ describe('as the same user', () => {
+ beforeEach(async () => {
+ const headers = await login(userParams)
+ client = new GraphQLClient(host, { headers })
+ })
+
+ it('name within specifications', async () => {
+ const expected = {
+ UpdateUser: {
+ id: 'u47',
+ name: 'John Doughnut',
+ },
+ }
+ await expect(client.request(mutation, variables)).resolves.toEqual(expected)
+ })
+
+ it('with no name', async () => {
+ const variables = {
+ id: 'u47',
+ name: null,
+ }
+ const expected = 'Username must be at least 3 characters long!'
+ await expect(client.request(mutation, variables)).rejects.toThrow(expected)
+ })
+
+ it('with too short name', async () => {
+ const variables = {
+ id: 'u47',
+ name: ' ',
+ }
+ const expected = 'Username must be at least 3 characters long!'
+ await expect(client.request(mutation, variables)).rejects.toThrow(expected)
+ })
+ })
+ })
+
+ describe('DeleteUser', () => {
+ let deleteUserVariables
+ let asAuthor
+ const deleteUserMutation = gql`
+ mutation($id: ID!, $resource: [String]) {
+ DeleteUser(id: $id, resource: $resource) {
+ id
+ contributions {
+ id
+ deleted
+ }
+ comments {
+ id
+ deleted
+ }
+ }
}
- const expected = 'Username must be at least 3 characters long!'
- await expect(client.request(mutation, variables)).rejects.toThrow(expected)
+ `
+ beforeEach(async () => {
+ asAuthor = await factory.create('User', {
+ email: 'test@example.org',
+ password: '1234',
+ id: 'u343',
+ })
+ await factory.create('User', {
+ email: 'friendsAccount@example.org',
+ password: '1234',
+ id: 'u565',
+ })
+ deleteUserVariables = { id: 'u343', resource: [] }
})
- it('with too short name', async () => {
- const variables = {
- id: 'u47',
- name: ' ',
- }
- const expected = 'Username must be at least 3 characters long!'
- await expect(client.request(mutation, variables)).rejects.toThrow(expected)
+ describe('unauthenticated', () => {
+ it('throws authorization error', async () => {
+ client = new GraphQLClient(host)
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
+ 'Not Authorised',
+ )
+ })
+ })
+
+ describe('authenticated', () => {
+ let headers
+ beforeEach(async () => {
+ headers = await login({
+ email: 'test@example.org',
+ password: '1234',
+ })
+ client = new GraphQLClient(host, { headers })
+ })
+
+ describe("attempting to delete another user's account", () => {
+ it('throws an authorization error', async () => {
+ deleteUserVariables = { id: 'u565' }
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).rejects.toThrow(
+ 'Not Authorised',
+ )
+ })
+ })
+
+ describe('attempting to delete my own account', () => {
+ let expectedResponse
+ beforeEach(async () => {
+ await asAuthor.authenticateAs({
+ email: 'test@example.org',
+ password: '1234',
+ })
+ await asAuthor.create('Post', {
+ id: 'p139',
+ content: 'Post by user u343',
+ })
+ await asAuthor.create('Comment', {
+ id: 'c155',
+ postId: 'p139',
+ content: 'Comment by user u343',
+ })
+ expectedResponse = {
+ DeleteUser: {
+ id: 'u343',
+ contributions: [{ id: 'p139', deleted: false }],
+ comments: [{ id: 'c155', deleted: false }],
+ },
+ }
+ })
+ it("deletes my account, but doesn't delete posts or comments by default", async () => {
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
+ expectedResponse,
+ )
+ })
+
+ describe("deletes a user's", () => {
+ it('posts on request', async () => {
+ deleteUserVariables = { id: 'u343', resource: ['Post'] }
+ expectedResponse = {
+ DeleteUser: {
+ id: 'u343',
+ contributions: [{ id: 'p139', deleted: true }],
+ comments: [{ id: 'c155', deleted: false }],
+ },
+ }
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
+ expectedResponse,
+ )
+ })
+
+ it('comments on request', async () => {
+ deleteUserVariables = { id: 'u343', resource: ['Comment'] }
+ expectedResponse = {
+ DeleteUser: {
+ id: 'u343',
+ contributions: [{ id: 'p139', deleted: false }],
+ comments: [{ id: 'c155', deleted: true }],
+ },
+ }
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
+ expectedResponse,
+ )
+ })
+
+ it('posts and comments on request', async () => {
+ deleteUserVariables = { id: 'u343', resource: ['Post', 'Comment'] }
+ expectedResponse = {
+ DeleteUser: {
+ id: 'u343',
+ contributions: [{ id: 'p139', deleted: true }],
+ comments: [{ id: 'c155', deleted: true }],
+ },
+ }
+ await expect(client.request(deleteUserMutation, deleteUserVariables)).resolves.toEqual(
+ expectedResponse,
+ )
+ })
+ })
+ })
})
})
})
diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql
index ab8b25399..2a8be9e09 100644
--- a/backend/src/schema/types/schema.gql
+++ b/backend/src/schema/types/schema.gql
@@ -4,8 +4,9 @@ type Query {
currentUser: User
# Get the latest Network Statistics
statistics: Statistics!
- findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
- statement: """
+ findPosts(filter: String!, limit: Int = 10): [Post]!
+ @cypher(
+ statement: """
CALL db.index.fulltext.queryNodes('full_text_search', $filter)
YIELD node as post, score
MATCH (post)<-[:WROTE]-(user:User)
@@ -14,8 +15,8 @@ type Query {
AND NOT post.deleted = true AND NOT post.disabled = true
RETURN post
LIMIT $limit
- """
- )
+ """
+ )
CommentByPost(postId: ID!): [Comment]!
}
@@ -23,7 +24,7 @@ type Mutation {
# Get a JWT Token for the given Email and password
login(email: String!, password: String!): String!
signup(email: String!, password: String!): Boolean!
- changePassword(oldPassword:String!, newPassword: String!): String!
+ changePassword(oldPassword: String!, newPassword: String!): String!
report(id: ID!, description: String): Report
disable(id: ID!): ID
enable(id: ID!): ID
@@ -37,6 +38,7 @@ type Mutation {
follow(id: ID!, type: FollowTypeEnum): Boolean!
# Unfollow the given Type and ID
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
+ DeleteUser(id: ID!, resource: [String]): User
}
type Statistics {
@@ -53,7 +55,7 @@ type Statistics {
type Notification {
id: ID!
- read: Boolean,
+ read: Boolean
user: User @relation(name: "NOTIFIED", direction: "OUT")
post: Post @relation(name: "NOTIFIED", direction: "IN")
createdAt: String
@@ -80,7 +82,8 @@ type Report {
id: ID!
submitter: User @relation(name: "REPORTED", direction: "IN")
description: String
- type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
+ type: String!
+ @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
createdAt: String
comment: Comment @relation(name: "REPORTED", direction: "OUT")
post: Post @relation(name: "REPORTED", direction: "OUT")
@@ -131,4 +134,3 @@ type SocialMedia {
url: String
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
}
-
diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql
index c402a1233..271d92750 100644
--- a/backend/src/schema/types/type/Post.gql
+++ b/backend/src/schema/types/type/Post.gql
@@ -15,29 +15,37 @@ type Post {
disabledBy: User @relation(name: "DISABLED", direction: "IN")
createdAt: String
updatedAt: String
-
- relatedContributions: [Post]! @cypher(
- statement: """
+ language: String
+ relatedContributions: [Post]!
+ @cypher(
+ statement: """
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
RETURN DISTINCT post
LIMIT 10
- """
- )
+ """
+ )
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
comments: [Comment]! @relation(name: "COMMENTS", direction: "IN")
- commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
+ commentsCount: Int!
+ @cypher(
+ statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)"
+ )
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
- shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
+ shoutedCount: Int!
+ @cypher(
+ statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
+ )
# Has the currently logged in user shouted that post?
- shoutedByCurrentUser: Boolean! @cypher(
- statement: """
+ shoutedByCurrentUser: Boolean!
+ @cypher(
+ statement: """
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
RETURN COUNT(u) >= 1
- """
- )
+ """
+ )
}
diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql
index 1287aa45f..6836f16fe 100644
--- a/backend/src/schema/types/type/User.gql
+++ b/backend/src/schema/types/type/User.gql
@@ -56,14 +56,14 @@ type User {
contributionsCount: Int! @cypher(
statement: """
MATCH (this)-[:WROTE]->(r:Post)
- WHERE (NOT exists(r.deleted) OR r.deleted = false)
- AND (NOT exists(r.disabled) OR r.disabled = false)
+ WHERE NOT r.deleted = true AND NOT r.disabled = true
RETURN COUNT(r)
"""
)
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
+ commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment)-[:COMMENTS]->(p:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true AND NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))")
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
@@ -77,4 +77,4 @@ type User {
badges: [Badge]! @relation(name: "REWARDED", direction: "IN")
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
-}
\ No newline at end of file
+}
diff --git a/backend/test/features/activity-follow.feature b/backend/test/features/activity-follow.feature
index 3cfe73340..7aa0c447d 100644
--- a/backend/test/features/activity-follow.feature
+++ b/backend/test/features/activity-follow.feature
@@ -10,6 +10,7 @@ Feature: Follow a user
| stuart-little |
| tero-vota |
+ @wip
Scenario: Send a follow to a user inbox and make sure it's added to the right followers collection
When I send a POST request with the following activity to "/activitypub/users/tero-vota/inbox":
"""
diff --git a/backend/test/features/activity-like.feature b/backend/test/features/activity-like.feature
index ec8c99110..26ef9c857 100644
--- a/backend/test/features/activity-like.feature
+++ b/backend/test/features/activity-like.feature
@@ -27,6 +27,7 @@ Feature: Like an object like an article or note
}
"""
+ @wip
Scenario: Send a like of a person to an users inbox and make sure it's added to the likes collection
When I send a POST request with the following activity to "/activitypub/users/karl-heinz/inbox":
"""
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 61803eb1f..ea5891a1f 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -1029,10 +1029,10 @@
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
-"@types/express@4.16.1":
- version "4.16.1"
- resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0"
- integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==
+"@types/express@4.17.0":
+ version "4.17.0"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
+ integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
@@ -1119,10 +1119,10 @@
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
-"@types/yup@0.26.14":
- version "0.26.14"
- resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.14.tgz#d31f3b9a04039cca70ebb4db4d6c7fc3f694e80b"
- integrity sha512-OcBtVLHvYULVSltpuBdhFiVOKoSsOS58D872HydO93oBf3OdGq5zb+LnqGo18TNNSV2aW8hjIdS6H+wp68zFtQ==
+"@types/yup@0.26.16":
+ version "0.26.16"
+ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
+ integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
"@types/zen-observable@^0.5.3":
version "0.5.4"
@@ -1322,10 +1322,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
apollo-utilities "^1.3.2"
tslib "^1.9.3"
-apollo-client@~2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
- integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
+apollo-client@~2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
+ integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
dependencies:
"@types/zen-observable" "^0.8.0"
apollo-cache "1.3.2"
@@ -1351,14 +1351,14 @@ apollo-engine-reporting-protobuf@0.3.1:
dependencies:
protobufjs "^6.8.6"
-apollo-engine-reporting@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.0.tgz#50151811a0f5e70f4a73e7092a61fec422d8e722"
- integrity sha512-xP+Z+wdQH4ee7xfuP3WsJcIe30AH68gpp2lQm2+rnW5JfjIqD5YehSoO2Svi2jK3CSv8Y561i3QMW9i34P7hEQ==
+apollo-engine-reporting@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.1.tgz#f2c2c63f865871a57c15cdbb2a3bcd4b4af28115"
+ integrity sha512-e0Xp+0yite8DH/xm9fnJt42CxfWAcY6waiq3icCMAgO9T7saXzVOPpl84SkuA+hIJUBtfaKrTnC+7Jxi/I7OrQ==
dependencies:
apollo-engine-reporting-protobuf "0.3.1"
apollo-graphql "^0.3.0"
- apollo-server-core "2.6.2"
+ apollo-server-core "2.6.3"
apollo-server-env "2.4.0"
async-retry "^1.2.1"
graphql-extensions "0.7.2"
@@ -1388,41 +1388,41 @@ apollo-graphql@^0.3.0:
apollo-env "0.5.1"
lodash.sortby "^4.7.0"
-apollo-link-context@~1.0.14:
- version "1.0.17"
- resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.17.tgz#439272cfb43ec1891506dd175ed907845b7de36c"
- integrity sha512-W5UUfHcrrlP5uqJs5X1zbf84AMXhPZGAqX/7AQDgR6wY/7//sMGfJvm36KDkpIeSOElztGtM9z6zdPN1NbT41Q==
+apollo-link-context@~1.0.18:
+ version "1.0.18"
+ resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.18.tgz#9e700e3314da8ded50057fee0a18af2bfcedbfc3"
+ integrity sha512-aG5cbUp1zqOHHQjAJXG7n/izeMQ6LApd/whEF5z6qZp5ATvcyfSNkCfy3KRJMMZZ3iNfVTs6jF+IUA8Zvf+zeg==
dependencies:
- apollo-link "^1.2.11"
+ apollo-link "^1.2.12"
tslib "^1.9.3"
-apollo-link-http-common@^0.2.13:
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
- integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
+apollo-link-http-common@^0.2.14:
+ version "0.2.14"
+ resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.14.tgz#d3a195c12e00f4e311c417f121181dcc31f7d0c8"
+ integrity sha512-v6mRU1oN6XuX8beVIRB6OpF4q1ULhSnmy7ScnHnuo1qV6GaFmDcbdvXqxIkAV1Q8SQCo2lsv4HeqJOWhFfApOg==
dependencies:
- apollo-link "^1.2.11"
- ts-invariant "^0.3.2"
+ apollo-link "^1.2.12"
+ ts-invariant "^0.4.0"
tslib "^1.9.3"
-apollo-link-http@~1.5.14:
- version "1.5.14"
- resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.14.tgz#ed6292248d1819ccd16523e346d35203a1b31109"
- integrity sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==
+apollo-link-http@~1.5.15:
+ version "1.5.15"
+ resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.15.tgz#106ab23bb8997bd55965d05855736d33119652cf"
+ integrity sha512-epZFhCKDjD7+oNTVK3P39pqWGn4LEhShAoA1Q9e2tDrBjItNfviiE33RmcLcCURDYyW5JA6SMgdODNI4Is8tvQ==
dependencies:
- apollo-link "^1.2.11"
- apollo-link-http-common "^0.2.13"
+ apollo-link "^1.2.12"
+ apollo-link-http-common "^0.2.14"
tslib "^1.9.3"
-apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.3:
- version "1.2.11"
- resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
- integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
+apollo-link@^1.0.0, apollo-link@^1.2.12, apollo-link@^1.2.3:
+ version "1.2.12"
+ resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.12.tgz#014b514fba95f1945c38ad4c216f31bcfee68429"
+ integrity sha512-fsgIAXPKThyMVEMWQsUN22AoQI+J/pVXcjRGAShtk97h7D8O+SPskFinCGEkxPeQpE83uKaqafB2IyWdjN+J3Q==
dependencies:
- apollo-utilities "^1.2.1"
- ts-invariant "^0.3.2"
+ apollo-utilities "^1.3.0"
+ ts-invariant "^0.4.0"
tslib "^1.9.3"
- zen-observable-ts "^0.8.18"
+ zen-observable-ts "^0.8.19"
apollo-server-caching@0.4.0:
version "0.4.0"
@@ -1431,17 +1431,17 @@ apollo-server-caching@0.4.0:
dependencies:
lru-cache "^5.0.0"
-apollo-server-core@2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.2.tgz#a792b50d4df9e26ec03759a31fbcbce38361b218"
- integrity sha512-AbAnfoQ26NPsNIyBa/BVKBtA/wRsNL/E6eEem1VIhzitfgO25bVXFbEZDLxbgz6wvJ+veyRFpse7Qi1bvRpxOw==
+apollo-server-core@2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.3.tgz#786c8251c82cf29acb5cae9635a321f0644332ae"
+ integrity sha512-tfC0QO1NbJW3ShkB5pRCnUaYEkW2AwnswaTeedkfv//EO3yiC/9LeouCK5F22T8stQG+vGjvCqf0C8ldI/XsIA==
dependencies:
"@apollographql/apollo-tools" "^0.3.6"
"@apollographql/graphql-playground-html" "1.6.20"
"@types/ws" "^6.0.0"
apollo-cache-control "0.7.2"
apollo-datasource "0.5.0"
- apollo-engine-reporting "1.3.0"
+ apollo-engine-reporting "1.3.1"
apollo-server-caching "0.4.0"
apollo-server-env "2.4.0"
apollo-server-errors "2.3.0"
@@ -1479,18 +1479,18 @@ apollo-server-errors@2.3.0:
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
-apollo-server-express@2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.2.tgz#526297c01a7a32fe9215566f9fd7ff92e82f1fa0"
- integrity sha512-nbL3noJ5KxKGg+hT8UsAA7++oHWq/KNSevfdCluWTfUNqH1vYRTvAnARx/6JM06S9zcPTfOLcqwHnDnY9zYFxA==
+apollo-server-express@2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.3.tgz#62034c978f84207615c0430fb37ab006f71146fe"
+ integrity sha512-8ca+VpKArgNzFar0D3DesWnn0g9YDtFLhO56TQprHh2Spxu9WxTnYNjsYs2MCCNf+iV/uy7vTvEknErvnIcZaQ==
dependencies:
"@apollographql/graphql-playground-html" "1.6.20"
"@types/accepts" "^1.3.5"
"@types/body-parser" "1.17.0"
"@types/cors" "^2.8.4"
- "@types/express" "4.16.1"
+ "@types/express" "4.17.0"
accepts "^1.3.5"
- apollo-server-core "2.6.2"
+ apollo-server-core "2.6.3"
body-parser "^1.18.3"
cors "^2.8.4"
graphql-subscriptions "^1.0.0"
@@ -1523,20 +1523,20 @@ apollo-server-plugin-base@0.5.2:
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.2.tgz#f97ba983f1e825fec49cba8ff6a23d00e1901819"
integrity sha512-j81CpadRLhxikBYHMh91X4aTxfzFnmmebEiIR9rruS6dywWCxV2aLW87l9ocD1MiueNam0ysdwZkX4F3D4csNw==
-apollo-server-testing@~2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.2.tgz#e0ecddd565fce1c38a346f9fbe6118f543ccf6a6"
- integrity sha512-I9QLFk4I/z9oOIXfnLc8RPBYAKih6Olrg3RDeRvWhDjLQ8gfALXVhCO+7WuvM35wNZcZVn7aXBeZ8Y3mlgkj8w==
+apollo-server-testing@~2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.3.tgz#a0199a5d42000e60ecf0dea44b851f5f581e280e"
+ integrity sha512-LTkegcGVSkM+pA0FINDSYVl3TiFYKZyfjlKrEr/LN6wLiL6gbRgy6LMtk2j+qli/bnTDqqQREX8OEqmV8FKUoQ==
dependencies:
- apollo-server-core "2.6.2"
+ apollo-server-core "2.6.3"
-apollo-server@~2.6.2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.2.tgz#33fe894b740588f059a7679346516ffce50377d5"
- integrity sha512-fMXaAKIb0dX0lzcZ4zlu7ay1L596d9HTNkdn8cKuM7zmTpugZSAL966COguJUDSjUS9CaB1Kh5hl1yRuRqHXSA==
+apollo-server@~2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.3.tgz#71235325449c6d3881a5143975ca44c07a07d2d7"
+ integrity sha512-pTIXE5xEMAikKLTIBIqLNvimMETiZbzmiqDb6BGzIUicAz4Rxa1/+bDi1ZeJWrZQjE/TfBLd2Si3qam7dZGrjw==
dependencies:
- apollo-server-core "2.6.2"
- apollo-server-express "2.6.2"
+ apollo-server-core "2.6.3"
+ apollo-server-express "2.6.3"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
@@ -1566,7 +1566,7 @@ apollo-upload-server@^7.0.0:
http-errors "^1.7.0"
object-path "^0.11.4"
-apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.2.1, apollo-utilities@^1.3.2:
+apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
@@ -1790,10 +1790,10 @@ babel-core@~7.0.0-0:
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-babel-eslint@~10.0.1:
- version "10.0.1"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
- integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
+babel-eslint@~10.0.2:
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
+ integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.0.0"
@@ -2586,10 +2586,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
-date-fns@2.0.0-alpha.31:
- version "2.0.0-alpha.31"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.31.tgz#51bcfdca25dfc9bea334a556ab33dfc0bb00421c"
- integrity sha512-S19PwMqnbYsqcbCg02Yj9gv4veVNZ0OX7v2+zcd+Mq0RI7LoDKJipJjnMrTZ3Cc6blDuTce5G/pHXcVIGRwJWQ==
+date-fns@2.0.0-alpha.34:
+ version "2.0.0-alpha.34"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.34.tgz#5d3ae7ca0d08915ccfc87a20545250af4e9c3cae"
+ integrity sha512-yjSYUHASHvzOZl++cEms+Tw7oQOFA+7Z6/lL7L3lRO9j6pMfT48N6oEyvCGo/MVlH08XWmydgf8X9Y1eedf9sQ==
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
@@ -2995,10 +2995,10 @@ escodegen@^1.9.1:
optionalDependencies:
source-map "~0.6.1"
-eslint-config-prettier@~4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
- integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
+eslint-config-prettier@~5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
+ integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
dependencies:
get-stdin "^6.0.0"
@@ -3772,12 +3772,12 @@ graphql-request@~1.8.2:
dependencies:
cross-fetch "2.2.2"
-graphql-shield@~5.3.6:
- version "5.3.6"
- resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.6.tgz#20061b02f77056c0870a623c530ef28a1bf4fff4"
- integrity sha512-ihw/i4X+d1kpj1SVA6iBkVl2DZhPsI+xV08geR2TX3FWhpU7zakk/16yBzDRJTTCUgKsWfgyebrgIBsuhTwMnA==
+graphql-shield@~5.3.8:
+ version "5.3.8"
+ resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.8.tgz#f9e7ad2285f6cfbe20a8a49154ce6c1b184e3893"
+ integrity sha512-33rQ8U5jMurHIapctHk7hBcUg3nxC7fmMIMtyWiomJXhBmztFq/SG7jNaapnL5M7Q/0BmoaSQd3FLSpelP9KPw==
dependencies:
- "@types/yup" "0.26.14"
+ "@types/yup" "0.26.16"
lightercollective "^0.3.0"
object-hash "^1.3.1"
yup "^0.27.0"
@@ -6230,10 +6230,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@~1.17.1:
- version "1.17.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db"
- integrity sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==
+prettier@~1.18.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
+ integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
pretty-format@^24.8.0:
version "24.8.0"
@@ -7543,13 +7543,6 @@ trunc-text@1.0.1:
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5"
integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU=
-ts-invariant@^0.3.2:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
- integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
- dependencies:
- tslib "^1.9.3"
-
ts-invariant@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.2.tgz#8685131b8083e67c66d602540e78763408be9113"
@@ -8112,10 +8105,10 @@ yup@^0.27.0:
synchronous-promise "^2.0.6"
toposort "^2.0.2"
-zen-observable-ts@^0.8.18:
- version "0.8.18"
- resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
- integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
+zen-observable-ts@^0.8.19:
+ version "0.8.19"
+ resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.19.tgz#c094cd20e83ddb02a11144a6e2a89706946b5694"
+ integrity sha512-u1a2rpE13G+jSzrg3aiCqXU5tN2kw41b+cBZGmnc+30YimdkKiDj9bTowcB41eL77/17RF/h+393AuVgShyheQ==
dependencies:
tslib "^1.9.3"
zen-observable "^0.8.0"
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/.env b/deployment/legacy-migration/maintenance-worker/binaries/.env
new file mode 100644
index 000000000..773918095
--- /dev/null
+++ b/deployment/legacy-migration/maintenance-worker/binaries/.env
@@ -0,0 +1,6 @@
+# SSH Access
+# SSH_USERNAME='username'
+# SSH_HOST='example.org'
+
+# UPLOADS_DIRECTORY=/var/www/api/uploads
+OUTPUT_DIRECTORY='/uploads/'
\ No newline at end of file
diff --git a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads
index 11fd81623..5c0b67d74 100755
--- a/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads
+++ b/deployment/legacy-migration/maintenance-worker/binaries/import_legacy_uploads
@@ -1,6 +1,11 @@
#!/usr/bin/env bash
set -e
+# import .env config
+set -o allexport
+source $(dirname "$0")/.env
+set +o allexport
+
for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
do
if [[ -z "${!var}" ]]; then
@@ -9,4 +14,4 @@ do
fi
done
-rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ /uploads/
+rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/ ${OUTPUT_DIRECTORY}
diff --git a/package.json b/package.json
index dd7454c54..fed6c742b 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
"codecov": "^3.5.0",
"cross-env": "^5.2.0",
"cypress": "^3.3.1",
- "cypress-cucumber-preprocessor": "^1.11.2",
- "cypress-file-upload": "^3.1.2",
+ "cypress-cucumber-preprocessor": "^1.12.0",
+ "cypress-file-upload": "^3.1.4",
"cypress-plugin-retries": "^1.2.2",
"dotenv": "^8.0.0",
"faker": "^4.1.0",
diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss
index db967e973..560249b4a 100644
--- a/webapp/assets/styles/main.scss
+++ b/webapp/assets/styles/main.scss
@@ -10,7 +10,7 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
&::before {
@include border-radius($border-radius-x-large);
box-shadow: inset 0 0 0 5px $color-danger;
- content: "";
+ content: '';
display: block;
position: absolute;
width: 100%;
@@ -102,10 +102,10 @@ hr {
height: 1px !important;
}
-[class$=menu-trigger] {
+[class$='menu-trigger'] {
user-select: none;
}
-[class$=menu-popover] {
+[class$='menu-popover'] {
display: inline-block;
nav {
@@ -145,10 +145,11 @@ hr {
}
}
-[class$="menu-popover"] {
+[class$='menu-popover'] {
min-width: 130px;
- a, button {
+ a,
+ button {
display: flex;
align-content: center;
align-items: center;
diff --git a/webapp/components/Avatar/Avatar.spec.js b/webapp/components/Avatar/Avatar.spec.js
index ae91fecfe..d3ebcb030 100644
--- a/webapp/components/Avatar/Avatar.spec.js
+++ b/webapp/components/Avatar/Avatar.spec.js
@@ -1,9 +1,11 @@
import { mount, createLocalVue } from '@vue/test-utils'
import Styleguide from '@human-connection/styleguide'
import Avatar from './Avatar.vue'
+import Filters from '~/plugins/vue-filters'
const localVue = createLocalVue()
localVue.use(Styleguide)
+localVue.use(Filters)
describe('Avatar.vue', () => {
let propsData = {}
diff --git a/webapp/components/Avatar/Avatar.vue b/webapp/components/Avatar/Avatar.vue
index 0d997c745..ec2f9b28b 100644
--- a/webapp/components/Avatar/Avatar.vue
+++ b/webapp/components/Avatar/Avatar.vue
@@ -1,5 +1,10 @@
-
- Mehr Informationen -
+Mehr Informationen
Hier findest du weitere infos zum Thema.
@@ -42,7 +40,7 @@ :key="relatedPost.id" :post="relatedPost" :width="{ base: '100%', lg: 1 }" - @deletePost="post.relatedContributions.splice(index, 1)" + @removePostFromList="post.relatedContributions.splice(index, 1)" />
diff --git a/webapp/pages/post/edit/_id.vue b/webapp/pages/post/edit/_id.vue
index 7dcff5036..150087ce2 100644
--- a/webapp/pages/post/edit/_id.vue
+++ b/webapp/pages/post/edit/_id.vue
@@ -3,9 +3,7 @@
-
-
-
+
+-
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+