resolve conflict

This commit is contained in:
ogerly 2022-09-20 10:29:37 +02:00
commit bfcfb8462e
111 changed files with 1271 additions and 681 deletions

View File

@ -1,13 +1,15 @@
## 🍰 Pullrequest ## 🍰 Pull Request
<!-- Describe the Pullrequest. Use Screenshots if possible. --> <!-- Describe the Pullrequest. Use Screenshots if possible. -->
XXX
### Issues ### Issues
<!-- Which Issues does this fix, which are related? <!-- Which Issues does this fix, which are related? -->
- fixes #XXX - fixes #XXX
- relates #XXX - relates #XXX
-->
- None
### Todo ### Todo
<!-- In case some parts are still missing, list them here. --> <!-- In case some parts are still missing, list them here. -->
- [X] None
- [ ] XXX list here …

View File

@ -60,6 +60,7 @@ your `.env` configuration file.
Your backend is up and running at [http://localhost:4000/](http://localhost:4000/) Your backend is up and running at [http://localhost:4000/](http://localhost:4000/)
This will start the GraphQL service \(by default on localhost:4000\) where you This will start the GraphQL service \(by default on localhost:4000\) where you
can issue GraphQL requests or access GraphQL Playground in the browser. can issue GraphQL requests or access GraphQL Playground in the browser.
More details about our GraphQL playground and how to use it with ocelot.social can be found [here](./src/graphql/GraphQL-Playground.md).
![GraphQL Playground](../.gitbook/assets/graphql-playground.png) ![GraphQL Playground](../.gitbook/assets/graphql-playground.png)

View File

@ -15,7 +15,7 @@
"dev": "nodemon --exec babel-node src/ -e js,gql", "dev": "nodemon --exec babel-node src/ -e js,gql",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql", "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
"lint": "eslint src --config .eslintrc.js", "lint": "eslint src --config .eslintrc.js",
"test": "cross-env NODE_ENV=test jest --forceExit --detectOpenHandles --runInBand --coverage", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --forceExit --detectOpenHandles --runInBand --coverage --logHeapUsage",
"db:clean": "babel-node src/db/clean.js", "db:clean": "babel-node src/db/clean.js",
"db:reset": "yarn run db:clean", "db:reset": "yarn run db:clean",
"db:seed": "babel-node src/db/seed.js", "db:seed": "babel-node src/db/seed.js",
@ -103,14 +103,14 @@
"mustache": "^4.2.0", "mustache": "^4.2.0",
"neo4j-driver": "^4.0.2", "neo4j-driver": "^4.0.2",
"neo4j-graphql-js": "^2.11.5", "neo4j-graphql-js": "^2.11.5",
"neode": "^0.4.7", "neode": "^0.4.8",
"node-fetch": "~2.6.1", "node-fetch": "~2.6.1",
"nodemailer": "^6.4.4", "nodemailer": "^6.4.4",
"nodemailer-html-to-text": "^3.2.0", "nodemailer-html-to-text": "^3.2.0",
"npm-run-all": "~4.1.5", "npm-run-all": "~4.1.5",
"request": "~2.88.2", "request": "~2.88.2",
"sanitize-html": "~1.22.0", "sanitize-html": "~1.22.0",
"slug": "~4.0.2", "slug": "~6.0.0",
"subscriptions-transport-ws": "^0.9.19", "subscriptions-transport-ws": "^0.9.19",
"trunc-html": "~1.1.2", "trunc-html": "~1.1.2",
"uuid": "~8.3.2", "uuid": "~8.3.2",
@ -129,7 +129,7 @@
"eslint-plugin-import": "~2.20.2", "eslint-plugin-import": "~2.20.2",
"eslint-plugin-jest": "~23.8.2", "eslint-plugin-jest": "~23.8.2",
"eslint-plugin-node": "~11.1.0", "eslint-plugin-node": "~11.1.0",
"eslint-plugin-prettier": "~3.1.2", "eslint-plugin-prettier": "~3.4.1",
"eslint-plugin-promise": "~4.3.1", "eslint-plugin-promise": "~4.3.1",
"eslint-plugin-standard": "~4.0.1", "eslint-plugin-standard": "~4.0.1",
"jest": "~25.3.0", "jest": "~25.3.0",

View File

@ -0,0 +1,98 @@
export const categories = [
{
icon: 'networking',
name: 'networking',
description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe',
},
{
icon: 'home',
name: 'home',
description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten',
},
{
icon: 'energy',
name: 'energy',
description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...',
},
{
icon: 'psyche',
name: 'psyche',
description: 'Seele, Gefühle, Glück',
},
{
icon: 'movement',
name: 'body-and-excercise',
description: 'Sport, Yoga, Massage, Tanzen, Entspannung',
},
{
icon: 'balance-scale',
name: 'law',
description: 'Menschenrechte, Gesetze, Verordnungen',
},
{
icon: 'finance',
name: 'finance',
description: 'Geld, Finanzsystem, Alternativwährungen, ...',
},
{
icon: 'child',
name: 'children',
description: 'Familie, Pädagogik, Schule, Prägung',
},
{
icon: 'mobility',
name: 'mobility',
description: 'Reise, Verkehr, Elektromobilität',
},
{
icon: 'shopping-cart',
name: 'economy',
description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...',
},
{
icon: 'peace',
name: 'peace',
description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken',
},
{
icon: 'politics',
name: 'politics',
description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien',
},
{
icon: 'nature',
name: 'nature',
description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt',
},
{
icon: 'science',
name: 'science',
description: 'Bildung, Hochschule, Publikationen, ...',
},
{
icon: 'health',
name: 'health',
description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...',
},
{
icon: 'media',
name: 'it-and-media',
description:
'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps',
},
{
icon: 'spirituality',
name: 'spirituality',
description: 'Religion, Werte, Ethik',
},
{
icon: 'culture',
name: 'culture',
description: 'Kunst, Theater, Musik, Fotografie, Film',
},
{
icon: 'miscellaneous',
name: 'miscellaneous',
description: '',
},
]

View File

@ -1,6 +1,8 @@
import { getDriver, getNeode } from '../../db/neo4j' import { getDriver, getNeode } from '../../db/neo4j'
import { hashSync } from 'bcryptjs' import { hashSync } from 'bcryptjs'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { categories } from '../../constants/categories'
import CONFIG from '../../config'
const defaultAdmin = { const defaultAdmin = {
email: 'admin@example.org', email: 'admin@example.org',
@ -10,6 +12,29 @@ const defaultAdmin = {
slug: 'admin', slug: 'admin',
} }
const createCategories = async (session) => {
const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => {
categories.forEach(({ icon, name }, index) => {
const id = `cat${index + 1}`
txc.run(
`MERGE (c:Category {
icon: "${icon}",
slug: "${name}",
name: "${name}",
id: "${id}",
createdAt: toString(datetime())
})`,
)
})
})
try {
await createCategoriesTxResultPromise
console.log('Successfully created categories!') // eslint-disable-line no-console
} catch (error) {
console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console
}
}
const createDefaultAdminUser = async (session) => { const createDefaultAdminUser = async (session) => {
const readTxResultPromise = session.readTransaction(async (txc) => { const readTxResultPromise = session.readTransaction(async (txc) => {
const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount') const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount')
@ -45,7 +70,7 @@ const createDefaultAdminUser = async (session) => {
}) })
try { try {
await createAdminTxResultPromise await createAdminTxResultPromise
console.log('Successfully created default admin user') // eslint-disable-line no-console console.log('Successfully created default admin user!') // eslint-disable-line no-console
} catch (error) { } catch (error) {
console.log(error) // eslint-disable-line no-console console.log(error) // eslint-disable-line no-console
} }
@ -58,6 +83,7 @@ class Store {
const { driver } = neode const { driver } = neode
const session = driver.session() const session = driver.session()
await createDefaultAdminUser(session) await createDefaultAdminUser(session)
if (CONFIG.CATEGORIES_ACTIVE) await createCategories(session)
const writeTxResultPromise = session.writeTransaction(async (txc) => { const writeTxResultPromise = session.writeTransaction(async (txc) => {
await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices
return Promise.all( return Promise.all(

View File

@ -6,6 +6,7 @@ import faker from '@faker-js/faker'
import Factory from '../db/factories' import Factory from '../db/factories'
import { getNeode, getDriver } from '../db/neo4j' import { getNeode, getDriver } from '../db/neo4j'
import { gql } from '../helpers/jest' import { gql } from '../helpers/jest'
import { categories } from '../constants/categories'
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
throw new Error(`You cannot seed the database in a non-staging and real production environment!`) throw new Error(`You cannot seed the database in a non-staging and real production environment!`)
@ -267,104 +268,16 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
dagobert.relateTo(louie, 'blocked'), dagobert.relateTo(louie, 'blocked'),
]) ])
await Promise.all([ await Promise.all(
Factory.build('category', { categories.map(({ icon, name }, index) => {
id: 'cat1', Factory.build('category', {
name: 'Just For Fun', id: `cat${index + 1}`,
slug: 'just-for-fun', slug: name,
icon: 'smile', name,
icon,
})
}), }),
Factory.build('category', { )
id: 'cat2',
name: 'Happiness & Values',
slug: 'happiness-values',
icon: 'heart-o',
}),
Factory.build('category', {
id: 'cat3',
name: 'Health & Wellbeing',
slug: 'health-wellbeing',
icon: 'medkit',
}),
Factory.build('category', {
id: 'cat4',
name: 'Environment & Nature',
slug: 'environment-nature',
icon: 'tree',
}),
Factory.build('category', {
id: 'cat5',
name: 'Animal Protection',
slug: 'animal-protection',
icon: 'paw',
}),
Factory.build('category', {
id: 'cat6',
name: 'Human Rights & Justice',
slug: 'human-rights-justice',
icon: 'balance-scale',
}),
Factory.build('category', {
id: 'cat7',
name: 'Education & Sciences',
slug: 'education-sciences',
icon: 'graduation-cap',
}),
Factory.build('category', {
id: 'cat8',
name: 'Cooperation & Development',
slug: 'cooperation-development',
icon: 'users',
}),
Factory.build('category', {
id: 'cat9',
name: 'Democracy & Politics',
slug: 'democracy-politics',
icon: 'university',
}),
Factory.build('category', {
id: 'cat10',
name: 'Economy & Finances',
slug: 'economy-finances',
icon: 'money',
}),
Factory.build('category', {
id: 'cat11',
name: 'Energy & Technology',
slug: 'energy-technology',
icon: 'flash',
}),
Factory.build('category', {
id: 'cat12',
name: 'IT, Internet & Data Privacy',
slug: 'it-internet-data-privacy',
icon: 'mouse-pointer',
}),
Factory.build('category', {
id: 'cat13',
name: 'Art, Culture & Sport',
slug: 'art-culture-sport',
icon: 'paint-brush',
}),
Factory.build('category', {
id: 'cat14',
name: 'Freedom of Speech',
slug: 'freedom-of-speech',
icon: 'bullhorn',
}),
Factory.build('category', {
id: 'cat15',
name: 'Consumption & Sustainability',
slug: 'consumption-sustainability',
icon: 'shopping-cart',
}),
Factory.build('category', {
id: 'cat16',
name: 'Global Peace & Nonviolence',
slug: 'global-peace-nonviolence',
icon: 'angellist',
}),
])
const [environment, nature, democracy, freedom] = await Promise.all([ const [environment, nature, democracy, freedom] = await Promise.all([
Factory.build('tag', { Factory.build('tag', {

View File

@ -0,0 +1,108 @@
# GraphQL Playground
To use GraphQL Playground, we need to know some basics:
## How To Login?
First, we need to have a user from ocelot.social to log in as.
The user can be created by seeding the Neo4j database from the backend or by multiple GraphQL mutations.
### Seed The Neo4j Database
In your browser you can reach the GraphQL Playground under `http://localhost:4000/`, if the database and the backend are running, see [backend](../../README.md).
There you will also find instructions on how to seed the database.
### Use GraphQL Mutations To Create A User
TODO: Describe how to create a user using GraphQL mutations!
### Login Via GraphQL
You can register a user by sending the query:
```gql
mutation {
login(email: "user@example.org", password: "1234")
}
```
Or use `"moderator@example.org"` or `"admin@example.org"` for the roll you need.
If all goes well, you will receive a QGL response like:
```json
{
"data": {
"login": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTY2MjAyMzMwNSwiZXhwIjoxNzI1MTM4NTA1LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.atBS-SOeS784HPeFl_5s8sRWehEAU1BkgcOZFD8d4bU"
}
}
```
You can use this response to set an HTTP header when you click `HTTP HEADERS` in the footer.
Just set it with the login token you received in response:
```json
{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTY2MjAyMzMwNSwiZXhwIjoxNzI1MTM4NTA1LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.atBS-SOeS784HPeFl_5s8sRWehEAU1BkgcOZFD8d4bU"
}
```
This token is used for all other queries and mutations you send to the backend.
## Query And Mutate
When you are logged in and open a new playground tab by clicking "+", you can create a new group by sending the following mutation:
```gql
mutation {
CreateGroup(
# id: ""
name: "My Group"
# slug: ""
about: "We will save the world"
description: "<p class=\"\"><em>English:</em></p><p class=\"\">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class=\"\">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class=\"\">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class=\"\">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>"
groupType: hidden
actionRadius: interplanetary
categoryIds: ["cat12"]
) {
id
name
slug
createdAt
updatedAt
disabled
deleted
about
description
groupType
actionRadius
myRole
}
}
```
You will receive the answer:
```json
{
"data": {
"CreateGroup": {
"id": "2e3bbadb-804b-4ebc-a673-2d7c7f05e827",
"name": "My Group",
"slug": "my-group",
"createdAt": "2022-09-01T09:44:47.969Z",
"updatedAt": "2022-09-01T09:44:47.969Z",
"disabled": false,
"deleted": false,
"about": "We will save the world",
"description": "<p class=\"\"><em>English:</em></p><p class=\"\">This group is hidden.</p><h3>What is our group for?</h3><p>This group was created to allow investigative journalists to share and collaborate.</p><h3>How does it work?</h3><p>Here you can internally share posts and comments about them.</p><p><br></p><p><em>Deutsch:</em></p><p class=\"\">Diese Gruppe ist verborgen.</p><h3>Wofür ist unsere Gruppe?</h3><p class=\"\">Diese Gruppe wurde geschaffen, um investigativen Journalisten den Austausch und die Zusammenarbeit zu ermöglichen.</p><h3>Wie funktioniert das?</h3><p class=\"\">Hier könnt ihr euch intern über Beiträge und Kommentare zu ihnen austauschen.</p>",
"groupType": "hidden",
"actionRadius": "interplanetary",
"myRole": "owner"
}
}
}
```
If you look into the Neo4j database with your browser and search the groups, you will now also find your new group.
For more details about our Neo4j database read [here](../../../neo4j/README.md).

View File

@ -178,6 +178,7 @@ export default shield(
GenerateInviteCode: isAuthenticated, GenerateInviteCode: isAuthenticated,
switchUserRole: isAdmin, switchUserRole: isAdmin,
markTeaserAsViewed: allow, markTeaserAsViewed: allow,
saveCategorySettings: isAuthenticated,
}, },
User: { User: {
email: or(isMyOwn, isAdmin), email: or(isMyOwn, isAdmin),
@ -188,5 +189,6 @@ export default shield(
debug, debug,
allowExternalErrors, allowExternalErrors,
fallbackRule: allow, fallbackRule: allow,
fallbackError: Error('Not Authorized!'),
}, },
) )

View File

@ -102,7 +102,7 @@ describe('authorization', () => {
await expect( await expect(
query({ query: userQuery, variables: { name: 'Owner' } }), query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { User: [null] }, data: { User: [null] },
}) })
}) })
@ -132,7 +132,7 @@ describe('authorization', () => {
await expect( await expect(
query({ query: userQuery, variables: { name: 'Owner' } }), query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { User: [null] }, data: { User: [null] },
}) })
}) })
@ -147,7 +147,7 @@ describe('authorization', () => {
await expect( await expect(
query({ query: userQuery, variables: { name: 'Owner' } }), query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { User: [null] }, data: { User: [null] },
}) })
}) })
@ -198,7 +198,7 @@ describe('authorization', () => {
it('denies permission', async () => { it('denies permission', async () => {
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { Signup: null }, data: { Signup: null },
}) })
}) })
@ -288,7 +288,7 @@ describe('authorization', () => {
it('denies permission', async () => { it('denies permission', async () => {
await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { Signup: null }, data: { Signup: null },
}) })
}) })

View File

@ -9,8 +9,7 @@ export default {
updatedAt: { updatedAt: {
type: 'string', type: 'string',
isoDate: true, isoDate: true,
required: true, required: false,
default: () => new Date().toISOString(),
}, },
post: { post: {
type: 'relationship', type: 'relationship',

View File

@ -88,10 +88,10 @@ describe('CreateComment', () => {
variables = { variables = {
...variables, ...variables,
postId: 'p1', postId: 'p1',
content: "I'm not authorised to comment", content: "I'm not authorized to comment",
} }
const { errors } = await mutate({ mutation: createCommentMutation, variables }) const { errors } = await mutate({ mutation: createCommentMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -107,14 +107,14 @@ describe('CreateComment', () => {
variables = { variables = {
...variables, ...variables,
postId: 'p1', postId: 'p1',
content: "I'm authorised to comment", content: "I'm authorized to comment",
} }
}) })
it('creates a comment', async () => { it('creates a comment', async () => {
await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: createCommentMutation, variables })).resolves.toMatchObject(
{ {
data: { CreateComment: { content: "I'm authorised to comment" } }, data: { CreateComment: { content: "I'm authorized to comment" } },
errors: undefined, errors: undefined,
}, },
) )
@ -150,7 +150,7 @@ describe('UpdateComment', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: updateCommentMutation, variables }) const { errors } = await mutate({ mutation: updateCommentMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -162,7 +162,7 @@ describe('UpdateComment', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: updateCommentMutation, variables }) const { errors } = await mutate({ mutation: updateCommentMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -217,7 +217,7 @@ describe('UpdateComment', () => {
it('returns null', async () => { it('returns null', async () => {
const { data, errors } = await mutate({ mutation: updateCommentMutation, variables }) const { data, errors } = await mutate({ mutation: updateCommentMutation, variables })
expect(data).toMatchObject({ UpdateComment: null }) expect(data).toMatchObject({ UpdateComment: null })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
}) })
@ -242,7 +242,7 @@ describe('DeleteComment', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const result = await mutate({ mutation: deleteCommentMutation, variables }) const result = await mutate({ mutation: deleteCommentMutation, variables })
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -254,7 +254,7 @@ describe('DeleteComment', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: deleteCommentMutation, variables }) const { errors } = await mutate({ mutation: deleteCommentMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })

View File

@ -72,7 +72,7 @@ describe('donations', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
authenticatedUser = undefined authenticatedUser = undefined
await expect(query({ query: donationsQuery, variables })).resolves.toMatchObject({ await expect(query({ query: donationsQuery, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -106,7 +106,7 @@ describe('donations', () => {
await expect( await expect(
mutate({ mutation: updateDonationsMutation, variables }), mutate({ mutation: updateDonationsMutation, variables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -126,7 +126,7 @@ describe('donations', () => {
mutate({ mutation: updateDonationsMutation, variables }), mutate({ mutation: updateDonationsMutation, variables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { UpdateDonations: null }, data: { UpdateDonations: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -145,7 +145,7 @@ describe('donations', () => {
mutate({ mutation: updateDonationsMutation, variables }), mutate({ mutation: updateDonationsMutation, variables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { UpdateDonations: null }, data: { UpdateDonations: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -63,7 +63,7 @@ describe('AddEmailAddress', () => {
it('throws AuthorizationError', async () => { it('throws AuthorizationError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { AddEmailAddress: null }, data: { AddEmailAddress: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -169,7 +169,7 @@ describe('VerifyEmailAddress', () => {
it('throws AuthorizationError', async () => { it('throws AuthorizationError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null }, data: { VerifyEmailAddress: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -117,7 +117,7 @@ describe('follow', () => {
variables, variables,
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { followUser: null }, data: { followUser: null },
}) })
}) })
@ -191,7 +191,7 @@ describe('follow', () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: mutationUnfollowUser, variables })).resolves.toMatchObject({
data: { unfollowUser: null }, data: { unfollowUser: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -120,7 +120,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: disableVariables }), mutate({ mutation: reviewMutation, variables: disableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -134,7 +134,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: disableVariables }), mutate({ mutation: reviewMutation, variables: disableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -218,7 +218,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: disableVariables }), mutate({ mutation: reviewMutation, variables: disableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -232,7 +232,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: disableVariables }), mutate({ mutation: reviewMutation, variables: disableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -488,7 +488,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: enableVariables }), mutate({ mutation: reviewMutation, variables: enableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -507,7 +507,7 @@ describe('moderate resources', () => {
await expect( await expect(
mutate({ mutation: reviewMutation, variables: enableVariables }), mutate({ mutation: reviewMutation, variables: enableVariables }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -165,7 +165,7 @@ describe('given some notifications', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await query({ query: notificationQuery }) const { errors } = await query({ query: notificationQuery })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -313,7 +313,7 @@ describe('given some notifications', () => {
mutation: markAsReadMutation, mutation: markAsReadMutation,
variables: { ...variables, id: 'p1' }, variables: { ...variables, id: 'p1' },
}) })
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })

View File

@ -358,7 +358,7 @@ export default {
undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'], undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'],
hasMany: { hasMany: {
tags: '-[:TAGGED]->(related:Tag)', tags: '-[:TAGGED]->(related:Tag)',
// categories: '-[:CATEGORIZED]->(related:Category)', categories: '-[:CATEGORIZED]->(related:Category)',
comments: '<-[:COMMENTS]-(related:Comment)', comments: '<-[:COMMENTS]-(related:Comment)',
shoutedBy: '<-[:SHOUTED]-(related:User)', shoutedBy: '<-[:SHOUTED]-(related:User)',
emotions: '<-[related:EMOTED]', emotions: '<-[related:EMOTED]',

View File

@ -281,7 +281,7 @@ describe('CreatePost', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: createPostMutation, variables }) const { errors } = await mutate({ mutation: createPostMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -369,7 +369,7 @@ describe('UpdatePost', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
authenticatedUser = null authenticatedUser = null
expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { UpdatePost: null }, data: { UpdatePost: null },
}) })
}) })
@ -382,7 +382,7 @@ describe('UpdatePost', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: updatePostMutation, variables }) const { errors } = await mutate({ mutation: updatePostMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -547,7 +547,7 @@ describe('pin posts', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { pinPost: null }, data: { pinPost: null },
}) })
}) })
@ -556,7 +556,7 @@ describe('pin posts', () => {
describe('ordinary users', () => { describe('ordinary users', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { pinPost: null }, data: { pinPost: null },
}) })
}) })
@ -571,7 +571,7 @@ describe('pin posts', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: pinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { pinPost: null }, data: { pinPost: null },
}) })
}) })
@ -854,7 +854,7 @@ describe('unpin posts', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { unpinPost: null }, data: { unpinPost: null },
}) })
}) })
@ -863,7 +863,7 @@ describe('unpin posts', () => {
describe('users cannot unpin posts', () => { describe('users cannot unpin posts', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { unpinPost: null }, data: { unpinPost: null },
}) })
}) })
@ -878,7 +878,7 @@ describe('unpin posts', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unpinPostMutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
data: { unpinPost: null }, data: { unpinPost: null },
}) })
}) })
@ -975,7 +975,7 @@ describe('DeletePost', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: deletePostMutation, variables }) const { errors } = await mutate({ mutation: deletePostMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -986,7 +986,7 @@ describe('DeletePost', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: deletePostMutation, variables }) const { errors } = await mutate({ mutation: deletePostMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -1128,7 +1128,7 @@ describe('emotions', () => {
variables, variables,
}) })
expect(addPostEmotions.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(addPostEmotions.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -1249,7 +1249,7 @@ describe('emotions', () => {
mutation: removePostEmotionsMutation, mutation: removePostEmotionsMutation,
variables: removePostEmotionsVariables, variables: removePostEmotionsVariables,
}) })
expect(removePostEmotions.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(removePostEmotions.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })

View File

@ -61,7 +61,7 @@ describe('Signup', () => {
CONFIG.INVITE_REGISTRATION = false CONFIG.INVITE_REGISTRATION = false
CONFIG.PUBLIC_REGISTRATION = false CONFIG.PUBLIC_REGISTRATION = false
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })

View File

@ -130,7 +130,7 @@ describe('file a report on a resource', () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({
data: { fileReport: null }, data: { fileReport: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -729,7 +729,7 @@ describe('file a report on a resource', () => {
authenticatedUser = null authenticatedUser = null
expect(query({ query: reportsQuery })).resolves.toMatchObject({ expect(query({ query: reportsQuery })).resolves.toMatchObject({
data: { reports: null }, data: { reports: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -739,7 +739,7 @@ describe('file a report on a resource', () => {
authenticatedUser = await currentUser.toJson() authenticatedUser = await currentUser.toJson()
expect(query({ query: reportsQuery })).resolves.toMatchObject({ expect(query({ query: reportsQuery })).resolves.toMatchObject({
data: { reports: null }, data: { reports: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })

View File

@ -97,7 +97,7 @@ describe('rewards', () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({
data: { reward: null }, data: { reward: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -255,7 +255,7 @@ describe('rewards', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({
data: { reward: null }, data: { reward: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -308,7 +308,7 @@ describe('rewards', () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({
data: { unreward: null }, data: { unreward: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -341,7 +341,7 @@ describe('rewards', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({
data: { unreward: null }, data: { unreward: null },
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -90,7 +90,7 @@ describe('shout and unshout posts', () => {
variables = { id: 'post-to-shout-id' } variables = { id: 'post-to-shout-id' }
authenticatedUser = undefined authenticatedUser = undefined
await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: mutationShoutPost, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })
@ -165,7 +165,7 @@ describe('shout and unshout posts', () => {
authenticatedUser = undefined authenticatedUser = undefined
variables = { id: 'post-to-shout-id' } variables = { id: 'post-to-shout-id' }
await expect(mutate({ mutation: mutationUnshoutPost, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: mutationUnshoutPost, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
}) })

View File

@ -94,7 +94,7 @@ describe('SocialMedia', () => {
const user = null const user = null
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -186,7 +186,7 @@ describe('SocialMedia', () => {
const user = null const user = null
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -195,7 +195,7 @@ describe('SocialMedia', () => {
const user = someUser const user = someUser
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -222,7 +222,7 @@ describe('SocialMedia', () => {
variables.id = 'some-id' variables.id = 'some-id'
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
}) })
@ -249,7 +249,7 @@ describe('SocialMedia', () => {
const user = null const user = null
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -258,7 +258,7 @@ describe('SocialMedia', () => {
const user = someUser const user = someUser
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, mutation, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })

View File

@ -20,16 +20,22 @@ export default {
const result = await transaction.run( const result = await transaction.run(
` `
MATCH (user:User {id: $id}) MATCH (user:User {id: $id})
WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] as media OPTIONAL MATCH (category:Category) WHERE NOT ((user)-[:NOT_INTERESTED_IN]->(category))
RETURN user {.*, socialMedia: media } as user OPTIONAL MATCH (cats:Category)
WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] AS media, category, toString(COUNT(cats)) AS categoryCount
RETURN user {.*, socialMedia: media, activeCategories: collect(category.id) } AS user, categoryCount
`, `,
{ id: user.id }, { id: user.id },
) )
log(result) const [categoryCount] = result.records.map((record) => record.get('categoryCount'))
return result.records.map((record) => record.get('user')) const [currentUser] = result.records.map((record) => record.get('user'))
// frontend expects empty array when all categories are selected
if (currentUser.activeCategories.length === parseInt(categoryCount))
currentUser.activeCategories = []
return currentUser
}) })
try { try {
const [currentUser] = await currentUserTransactionPromise const currentUser = await currentUserTransactionPromise
return currentUser return currentUser
} finally { } finally {
session.close() session.close()

View File

@ -6,6 +6,7 @@ import { createTestClient } from 'apollo-server-testing'
import createServer, { context } from '../../server' import createServer, { context } from '../../server'
import encode from '../../jwt/encode' import encode from '../../jwt/encode'
import { getNeode } from '../../db/neo4j' import { getNeode } from '../../db/neo4j'
import { categories } from '../../constants/categories'
const neode = getNeode() const neode = getNeode()
let query, mutate, variables, req, user let query, mutate, variables, req, user
@ -118,6 +119,7 @@ describe('currentUser', () => {
} }
email email
role role
activeCategories
} }
} }
` `
@ -172,6 +174,52 @@ describe('currentUser', () => {
} }
await respondsWith(expected) await respondsWith(expected)
}) })
describe('with categories in DB', () => {
beforeEach(async () => {
await Promise.all(
categories.map(async ({ icon, name }, index) => {
await Factory.build('category', {
id: `cat${index + 1}`,
slug: name,
name,
icon,
})
}),
)
})
it('returns empty array for all categories', async () => {
await respondsWith({
data: {
currentUser: expect.objectContaining({ activeCategories: [] }),
},
})
})
describe('with categories saved for current user', () => {
const saveCategorySettings = gql`
mutation ($activeCategories: [String]) {
saveCategorySettings(activeCategories: $activeCategories)
}
`
beforeEach(async () => {
await mutate({
mutation: saveCategorySettings,
variables: { activeCategories: ['cat1', 'cat3', 'cat5', 'cat7'] },
})
})
it('returns only the saved active categories', async () => {
const result = await query({ query: currentUserQuery, variables })
expect(result.data.currentUser.activeCategories).toHaveLength(4)
expect(result.data.currentUser.activeCategories).toContain('cat1')
expect(result.data.currentUser.activeCategories).toContain('cat3')
expect(result.data.currentUser.activeCategories).toContain('cat5')
expect(result.data.currentUser.activeCategories).toContain('cat7')
})
})
})
}) })
}) })
}) })
@ -310,8 +358,8 @@ describe('change password', () => {
}) })
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws "Not Authorised!"', async () => { it('throws "Not Authorized!"', async () => {
await respondsWith({ errors: [{ message: 'Not Authorised!' }] }) await respondsWith({ errors: [{ message: 'Not Authorized!' }] })
}) })
}) })

View File

@ -269,6 +269,49 @@ export default {
session.close() session.close()
} }
}, },
saveCategorySettings: async (object, args, context, resolveInfo) => {
const { activeCategories } = args
const {
user: { id },
} = context
const session = context.driver.session()
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(category:Category)
DELETE previousCategories
RETURN user, category
`,
{ id },
)
})
// frontend gives [] when all categories are selected (default)
if (activeCategories.length === 0) return true
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const saveCategorySettingsResponse = await transaction.run(
`
MATCH (category:Category) WHERE NOT category.id IN $activeCategories
MATCH (user:User { id: $id })
MERGE (user)-[r:NOT_INTERESTED_IN]->(category)
RETURN user, r, category
`,
{ id, activeCategories },
)
const [user] = await saveCategorySettingsResponse.records.map((record) =>
record.get('user'),
)
return user
})
try {
await writeTxResultPromise
return true
} finally {
session.close()
}
},
}, },
User: { User: {
email: async (parent, params, context, resolveInfo) => { email: async (parent, params, context, resolveInfo) => {

View File

@ -3,6 +3,7 @@ import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j' import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server' import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import { categories } from '../../constants/categories'
const categoryIds = ['cat9'] const categoryIds = ['cat9']
let user let user
@ -56,6 +57,12 @@ const switchUserRoleMutation = gql`
} }
` `
const saveCategorySettings = gql`
mutation ($activeCategories: [String]) {
saveCategorySettings(activeCategories: $activeCategories)
}
`
beforeAll(async () => { beforeAll(async () => {
await cleanDatabase() await cleanDatabase()
@ -101,7 +108,7 @@ describe('User', () => {
it('is forbidden', async () => { it('is forbidden', async () => {
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({ await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }], errors: [{ message: 'Not Authorized!' }],
}) })
}) })
@ -207,7 +214,7 @@ describe('UpdateUser', () => {
it('is not allowed to change other user accounts', async () => { it('is not allowed to change other user accounts', async () => {
const { errors } = await mutate({ mutation: updateUserMutation, variables }) const { errors } = await mutate({ mutation: updateUserMutation, variables })
expect(errors[0]).toHaveProperty('message', 'Not Authorised!') expect(errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -500,7 +507,7 @@ describe('switch user role', () => {
expect.objectContaining({ expect.objectContaining({
errors: [ errors: [
expect.objectContaining({ expect.objectContaining({
message: 'Not Authorised!', message: 'Not Authorized!',
}), }),
], ],
}), }),
@ -544,3 +551,140 @@ describe('switch user role', () => {
}) })
}) })
}) })
describe('save category settings', () => {
beforeEach(async () => {
await Promise.all(
categories.map(({ icon, name }, index) => {
Factory.build('category', {
id: `cat${index + 1}`,
slug: name,
name,
icon,
})
}),
)
})
beforeEach(async () => {
user = await Factory.build('user', {
id: 'user',
role: 'user',
})
variables = {
activeCategories: ['cat1', 'cat3', 'cat5'],
}
})
describe('not authenticated', () => {
beforeEach(async () => {
authenticatedUser = undefined
})
it('throws an error', async () => {
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
expect.objectContaining({
errors: [
expect.objectContaining({
message: 'Not Authorized!',
}),
],
}),
)
})
})
describe('authenticated', () => {
beforeEach(async () => {
authenticatedUser = await user.toJson()
})
const userQuery = gql`
query ($id: ID) {
User(id: $id) {
activeCategories
}
}
`
describe('no categories saved', () => {
it('returns true for active categories mutation', async () => {
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
expect.objectContaining({
data: { saveCategorySettings: true },
}),
)
})
describe('query for user', () => {
beforeEach(async () => {
await mutate({ mutation: saveCategorySettings, variables })
})
it('returns the active categories when user is queried', async () => {
await expect(
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']),
},
],
},
}),
)
})
})
})
describe('categories already saved', () => {
beforeEach(async () => {
variables = {
activeCategories: ['cat1', 'cat3', 'cat5'],
}
await mutate({ mutation: saveCategorySettings, variables })
variables = {
activeCategories: ['cat10', 'cat11', 'cat12', 'cat8', 'cat9'],
}
})
it('returns true', async () => {
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
expect.objectContaining({
data: { saveCategorySettings: true },
}),
)
})
describe('query for user', () => {
beforeEach(async () => {
await mutate({ mutation: saveCategorySettings, variables })
})
it('returns the new active categories when user is queried', async () => {
await expect(
query({ query: userQuery, variables: { id: authenticatedUser.id } }),
).resolves.toEqual(
expect.objectContaining({
data: {
User: [
{
activeCategories: expect.arrayContaining([
'cat10',
'cat11',
'cat12',
'cat8',
'cat9',
]),
},
],
},
}),
)
})
})
})
})
})

View File

@ -58,7 +58,7 @@ describe('mutedUsers', () => {
it('throws permission error', async () => { it('throws permission error', async () => {
const { query } = createTestClient(server) const { query } = createTestClient(server)
const result = await query({ query: mutedUserQuery }) const result = await query({ query: mutedUserQuery })
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
describe('authenticated and given a muted user', () => { describe('authenticated and given a muted user', () => {
@ -116,7 +116,7 @@ describe('muteUser', () => {
it('throws permission error', async () => { it('throws permission error', async () => {
const result = await muteAction({ id: 'u2' }) const result = await muteAction({ id: 'u2' })
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
describe('authenticated', () => { describe('authenticated', () => {
@ -333,7 +333,7 @@ describe('unmuteUser', () => {
it('throws permission error', async () => { it('throws permission error', async () => {
const result = await unmuteAction({ id: 'u2' }) const result = await unmuteAction({ id: 'u2' })
expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
describe('authenticated', () => { describe('authenticated', () => {

View File

@ -114,6 +114,14 @@ type User {
badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
emotions: [EMOTED] emotions: [EMOTED]
activeCategories: [String] @cypher(
statement: """
MATCH (category:Category)
WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category))
RETURN collect(category.id)
"""
)
} }
@ -220,4 +228,6 @@ type Mutation {
unblockUser(id: ID!): User unblockUser(id: ID!): User
switchUserRole(role: UserRole!, id: ID!): User switchUserRole(role: UserRole!, id: ID!): User
saveCategorySettings(activeCategories: [String]): Boolean
} }

View File

@ -4253,10 +4253,10 @@ eslint-plugin-node@~11.1.0:
resolve "^1.10.1" resolve "^1.10.1"
semver "^6.1.0" semver "^6.1.0"
eslint-plugin-prettier@~3.1.2: eslint-plugin-prettier@~3.4.1:
version "3.1.2" version "3.4.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5"
integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==
dependencies: dependencies:
prettier-linter-helpers "^1.0.0" prettier-linter-helpers "^1.0.0"
@ -7574,10 +7574,10 @@ neo4j-graphql-js@^2.11.5:
lodash "^4.17.15" lodash "^4.17.15"
neo4j-driver "^4.0.1" neo4j-driver "^4.0.1"
neode@^0.4.7: neode@^0.4.8:
version "0.4.7" version "0.4.8"
resolved "https://registry.yarnpkg.com/neode/-/neode-0.4.7.tgz#033007b57a2ee167e9ee5537493086db08d005eb" resolved "https://registry.yarnpkg.com/neode/-/neode-0.4.8.tgz#0889b4fc7f1bf0b470b01fa5b8870373b5d47ad6"
integrity sha512-YXlc187JRpeKCBcUIkY6nimXXG+Tvlopfe71/FPno2THrwmYt5mm0RPHZ+mXF2O1Xg6zvjKvOpCpDz2vHBfroQ== integrity sha512-pb91NfCOg4Fj5o+98H+S2XYC+ByQfbdhwcc1UVuzuUQ0Ezzj+jWz8NmKWU8ZfCH6l4plk71yDAPd2eTwpt+Xvg==
dependencies: dependencies:
"@hapi/joi" "^15.1.1" "@hapi/joi" "^15.1.1"
dotenv "^4.0.0" dotenv "^4.0.0"
@ -9262,10 +9262,10 @@ slug@^0.9.2:
dependencies: dependencies:
unicode ">= 0.3.1" unicode ">= 0.3.1"
slug@~4.0.2: slug@~6.0.0:
version "4.0.2" version "6.0.0"
resolved "https://registry.yarnpkg.com/slug/-/slug-4.0.2.tgz#35a62b4e71582778ac08bb30a1bf439fd0a43ea7" resolved "https://registry.yarnpkg.com/slug/-/slug-6.0.0.tgz#39637b32e5a873bc692812a630842880499ed6c9"
integrity sha512-c5XbWkwxHU13gAdSvBHQgnGy2sxv/REMz0ugcM0SOSBCO/N4wfU0TDBC3pgdOwVGjZwGnLBTRljXzdVYE+KYNw== integrity sha512-0MpNLyCSUSf0G1nAZmp9gY1cvesPP35a1Live25vZ23gWQ5SAopF0N+0hk9KI4ytNuTebJrHGNrgTnxboofcSg==
smart-buffer@^4.1.0: smart-buffer@^4.1.0:
version "4.1.0" version "4.1.0"

View File

@ -42,10 +42,10 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"mock-socket": "^9.0.3", "mock-socket": "^9.0.3",
"neo4j-driver": "^4.3.4", "neo4j-driver": "^4.3.4",
"neode": "^0.4.7", "neode": "^0.4.8",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rosie": "^2.1.0", "rosie": "^2.1.0",
"slug": "^5.1.0" "slug": "^6.0.0"
}, },
"resolutions": { "resolutions": {
"set-value": "^2.0.1" "set-value": "^2.0.1"

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>child</title>
<path d="M12 3c2.202 0 3.791 1.007 4.531 2.313 0.026-0.041 0.034-0.084 0.063-0.125 0.453-0.641 1.315-1.188 2.406-1.188v2c-0.453 0-0.588 0.111-0.719 0.281 3.845 0.921 6.812 4.105 7.563 8.063 1.193 0.397 2.156 1.337 2.156 2.656 0 1.365-1.024 2.33-2.281 2.688-0.816 4.701-4.82 8.313-9.719 8.313s-8.903-3.611-9.719-8.313c-1.257-0.357-2.281-1.323-2.281-2.688s1.024-2.33 2.281-2.688c0.741-4.271 4.122-7.637 8.406-8.219-0.39-0.574-1.192-1.094-2.688-1.094v-2zM16 8c-4.093 0-7.461 3.121-7.906 7.125l-0.094 0.875h-1c-0.555 0-1 0.445-1 1s0.445 1 1 1h1l0.094 0.875c0.445 4.004 3.813 7.125 7.906 7.125s7.461-3.121 7.906-7.125l0.094-0.875h1c0.555 0 1-0.445 1-1s-0.445-1-1-1h-0.875l-0.125-0.875c-0.536-4.019-3.907-7.125-8-7.125zM12.5 16c0.828 0 1.5 0.672 1.5 1.5s-0.672 1.5-1.5 1.5-1.5-0.672-1.5-1.5 0.672-1.5 1.5-1.5zM19.5 16c0.828 0 1.5 0.672 1.5 1.5s-0.672 1.5-1.5 1.5-1.5-0.672-1.5-1.5 0.672-1.5 1.5-1.5z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,20 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M5.9,25.1c-0.3,0.3-0.3,0.8,0,1.1s0.8,0.3,1.1,0c1.7-1.7,4.5-1.7,6.3,0c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2
c0.3-0.3,0.3-0.8,0-1.1C12,22.8,8.2,22.8,5.9,25.1z"/>
<path d="M24.4,8.7c0.7-0.7,2-0.7,2.7,0C27.2,8.9,27.4,9,27.6,9c0.2,0,0.4-0.1,0.5-0.2c0.3-0.3,0.3-0.8,0-1.1
c-1.3-1.3-3.5-1.3-4.8,0C23,8,23,8.5,23.3,8.7C23.6,9,24.1,9,24.4,8.7z"/>
<path d="M16.4,7.7c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.7-0.7,1.9-0.7,2.7,0C20.3,8.9,20.5,9,20.7,9s0.4-0.1,0.5-0.2
c0.3-0.3,0.3-0.8,0-1.1C19.9,6.4,17.7,6.4,16.4,7.7z"/>
<path d="M31.4,0.8c-0.2-0.1-0.5-0.2-0.7-0.1c-2,0.8-5.1,1.2-8.4,1.2s-6.4-0.4-8.4-1.2c-0.2-0.1-0.5-0.1-0.7,0.1
c-0.2,0.1-0.3,0.4-0.3,0.6v9.9c-1,0.1-1.9,0.1-3,0.1c-3.3,0-6.4-0.4-8.4-1.2c-0.2-0.1-0.5-0.1-0.7,0.1c-0.2,0.1-0.3,0.4-0.3,0.6
l0,11.5c0,5.2,4.2,9.4,9.4,9.4s9.4-4.2,9.4-9.4V22c0.7,0.3,1.6,0.4,3,0.4c5.2,0,9.4-4.2,9.4-9.4V1.4C31.7,1.2,31.6,0.9,31.4,0.8z
M9.9,30.4c-4.4,0-7.9-3.6-7.9-7.9V12c2.1,0.6,4.9,1,7.9,1c2.7,0,5.2-0.3,7.3-0.8l0.5-0.1c0,1.6,0,2.9,0,4c0,1.3,0,2.4,0.1,3.2v3.2
C17.8,26.8,14.2,30.4,9.9,30.4z M30.2,13c0,4.4-3.6,7.9-7.9,7.9c-2.1,0-2.8,0-3-1.7v-2.8c0.9,0.5,1.9,0.8,3,0.8
c1.6,0,3.1-0.6,4.2-1.7c0.3-0.3,0.3-0.8,0-1.1s-0.8-0.3-1.1,0c-0.8,0.8-2,1.3-3.1,1.3c-1.1,0-2.1-0.4-3-1.2v-3.6
c0-0.2-0.1-0.5-0.3-0.6c-0.2-0.1-0.5-0.2-0.7-0.1c-0.4,0.2-0.9,0.3-1.4,0.4l-2.5,0.4V2.5c2.1,0.6,4.9,1,7.9,1c3,0,5.8-0.3,7.9-1V13
z"/>
<path d="M10.9,17.7c-0.3,0.3-0.3,0.8,0,1.1s0.8,0.3,1.1,0c0.7-0.7,2-0.7,2.7,0c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2
c0.3-0.3,0.3-0.8,0-1.1C14.4,16.4,12.2,16.4,10.9,17.7z"/>
<path d="M7.7,18.7C7.9,18.9,8.1,19,8.3,19s0.4-0.1,0.5-0.2c0.3-0.3,0.3-0.8,0-1.1c-1.3-1.3-3.5-1.3-4.8,0c-0.3,0.3-0.3,0.8,0,1.1
s0.8,0.3,1.1,0C5.8,18,7,18,7.7,18.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>desktop</title>
<path d="M2 6h28v18h-13v2h5v2h-12v-2h5v-2h-13v-18zM4 8v14h24v-14h-24z"></path>
</svg>

After

Width:  |  Height:  |  Size: 240 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>ellipsis-h</title>
<path d="M6 14c1.105 0 2 0.895 2 2s-0.895 2-2 2-2-0.895-2-2 0.895-2 2-2zM16 14c1.105 0 2 0.895 2 2s-0.895 2-2 2-2-0.895-2-2 0.895-2 2-2zM26 14c1.105 0 2 0.895 2 2s-0.895 2-2 2-2-0.895-2-2 0.895-2 2-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@ -0,0 +1,14 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M27.6,19.7L26.8,19c-3-2.8-4.8-4.4-5.2-4.6c-0.9-0.6-2.9-0.5-3.6-0.4c-0.2-0.5-0.5-0.9-0.9-1.2V2.2
c0-1-0.7-1.6-1.3-1.6c-0.6-0.1-1.4,0.4-1.6,1.4c-0.1,0.5-0.3,1.4-0.5,2.4c-0.6,2.9-1,5-1,5.4c-0.1,1.1,1.1,2.8,1.5,3.3
c-0.3,0.4-0.5,0.9-0.5,1.4c0,0,0,0,0,0.1c-0.9,0.6-7.3,5-8.7,6c-0.8,0.6-0.9,1.4-0.6,2c0.2,0.4,0.7,0.7,1.2,0.7
c0.3,0,0.5-0.1,0.8-0.2c1.5-0.6,6.7-2.9,7.4-3.2c0.2-0.1,0.4-0.3,0.6-0.5l-1.3,11c0,1,2.3,1,2.8,1s2.8,0,2.8-1.1l-1.4-13.5
c1.8,1.1,7.2,4.4,8.6,5.2c0.3,0.2,0.7,0.3,0.9,0.3c0.5,0,0.9-0.2,1.1-0.5C28.5,21.4,28.5,20.5,27.6,19.7z M14.8,14.6
c0-0.8,0.6-1.2,1.2-1.2c0.6,0,1.2,0.6,1.2,1.2c0,0.8-0.6,1.2-1.2,1.2C15.2,15.8,14.8,15.2,14.8,14.6z M14.8,4.7c0.2-1,0.4-2,0.5-2.5
c0.1-0.5,0.3-0.7,0.5-0.6c0.2,0,0.4,0.2,0.4,0.6v10.2c-0.1,0-0.1,0-0.2,0c-0.3,0-0.6,0.1-0.9,0.2c-0.6-0.8-1.4-2-1.3-2.7
C13.8,9.5,14.4,6.5,14.8,4.7z M13.5,19.1c-0.6,0.3-5.8,2.6-7.3,3.2c-0.4,0.2-0.7,0.1-0.8-0.1c-0.1-0.2,0-0.4,0.3-0.7
c1.3-0.9,6.9-4.8,8.4-5.8c0.2,0.3,0.5,0.6,0.8,0.8C14.6,17.4,13.9,18.8,13.5,19.1z M16,30.4c-0.6,0-1.4-0.1-1.8-0.2l1.6-13.4
c0,0,0.1,0,0.1,0c0.1,0,0.3,0,0.4,0l1.4,13.4C17.4,30.3,16.6,30.4,16,30.4z M27.3,21.3c-0.1,0.1-0.4,0.2-0.7,0
c-1.5-0.9-7.2-4.4-8.8-5.3c0.2-0.3,0.3-0.6,0.4-0.9c1-0.1,2.5-0.1,3,0.3c0.4,0.2,3.6,3.2,5,4.5l0.8,0.7
C27.3,20.8,27.4,21.1,27.3,21.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,13 @@
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
<rect x="8.1" y="13.3" width="1.5" height="1.9"/>
<path d="M31.9,17.9L31.9,17.9l0-0.6c-0.2-5.5-4.1-10.3-9.5-11.6c-0.2,0-0.8-0.2-0.8-0.2c-3.1-0.6-6.3-0.5-9.5,0.1
c-0.3,0.1-0.6,0.1-0.9,0.2C10.6,5.7,10,5.4,9.7,5.4L9.5,5.3C8.7,5,7.6,4.6,6.8,5.1C6,5.7,6,6.7,6.2,7.3l0.3,1.2
C5.1,9.7,4,11.2,3.3,12.8c-0.4,0.8-0.8,0.8-1.2,0.8c-0.1,0-0.2,0-0.3,0H0l0,5.5l0.6,0.1c0,0,1.1,0.2,1.6,1C2.5,20.7,2.7,21,3,22.3
c1.1,3.4,3.6,5.3,4.6,5.9l0,3.9H12l2.6-3.4h6.1l1.7,3.4h4.9v-4.6l0.4-0.4c2.5-2.2,3.9-5.2,4.1-8.5l0-0.4
C31.9,18.1,31.9,18,31.9,17.9z M26.7,25.9l-0.9,0.9v3.8h-2.5l-1.7-3.4h-7.7l-2.6,3.4H9.1l0-3.2l-0.4-0.2c0,0-3.1-1.7-4.2-5.3
c-0.4-1.4-0.6-1.8-1-2.3c-0.5-0.9-1.4-1.3-2-1.5v-2.8l0.6,0c0.5,0,1.8,0,2.6-1.7c0.5-1.2,1.3-2.3,2.2-3.3c0.8-0.8,1.3-1,1.1-1.5
l0,0L7.6,7c-0.1-0.2-0.1-0.5,0-0.6c0.2-0.1,1,0.1,1.4,0.3l0.3,0.1c0.3,0.1,0.9,0.4,1.7,0.6l0.2,0.1l0.3-0.1c0.3-0.1,0.7-0.2,1-0.2
c3-0.6,5.9-0.6,8.9-0.1c0,0,0.6,0.1,0.8,0.1c4.7,1.1,8.2,5.3,8.4,10.1l0,0.8c-0.1,1.3-1.2,2.4-2.5,2.4c-1,0-1.9-0.9-1.9-2
c0-0.8,0.6-1.4,1.4-1.4v-1.5c-1.6,0-2.9,1.3-2.9,2.9c0,1.9,1.5,3.5,3.4,3.5c0.7,0,1.3-0.2,1.8-0.5C29.1,23.2,28.1,24.7,26.7,25.9z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,7 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M26.1,20.7c-2.9,5.1-9.3,8.8-10.8,9.6c-2.1-1.1-13.7-7.8-13.7-16.1c0-3.6,2.8-6.5,6.5-6.5c2.6,0,4.9,1.5,5.9,3.9l0.7,1.7
l0.7-1.7c1-2.4,3.3-3.9,5.9-3.9c3.6,0,6.5,2.8,6.5,6.5V15h1.5v-0.8c0-4.5-3.5-8-8-8c-2.7,0-5.2,1.3-6.6,3.5
c-1.4-2.2-3.9-3.5-6.6-3.5c-4.5,0-8,3.5-8,8c0,10,14.3,17.3,14.9,17.6l0.3,0.2l0.3-0.2c0.3-0.2,8.3-4.2,11.8-10.4l0.4-0.7L26.5,20
L26.1,20.7z"/>
<polygon points="22.9,17 19.5,21.2 17,14.5 11.6,19.9 12.6,20.9 16.4,17.2 19,24.2 23.6,18.5 31.9,18.5 31.9,17 "/>
</svg>

After

Width:  |  Height:  |  Size: 625 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>home</title>
<path d="M16 2.594l0.719 0.688 13 13-1.438 1.438-1.281-1.281v11.563h-9v-10h-4v10h-9v-11.563l-1.281 1.281-1.438-1.438 13-13zM16 5.438l-9 9v11.563h5v-10h8v10h5v-11.563z"></path>
</svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>lightbulb-o</title>
<path d="M6.813 2.406l2.094 2.094-1.406 1.406-2.094-2.094zM25.188 2.406l1.406 1.406-2.094 2.094-1.406-1.406zM16 3.031c4.934-0.047 9 4.027 9 8.969 0 2.706-1.249 5.062-2.906 6.719-1.238 1.15-2 2.627-2 4.094v1.188h-0.094v4h-2.281c-0.347 0.597-0.982 1-1.719 1s-1.372-0.403-1.719-1h-2.281v-6c-0.203-1.117-0.794-2.212-1.75-3.031-2.233-1.898-3.573-4.845-3.125-8.094 0.561-4.039 3.789-7.316 7.844-7.781 0.011-0.001 0.020 0.001 0.031 0 0.336-0.041 0.671-0.059 1-0.063zM16 5.031c-0.258 0.004-0.518 0.030-0.781 0.063-3.131 0.348-5.687 2.881-6.125 6.031-0.352 2.552 0.702 4.811 2.469 6.313 1.388 1.19 2.124 2.848 2.344 4.563h4.375c0.236-1.792 1.094-3.434 2.438-4.688l-0.031-0.031c1.343-1.343 2.313-3.187 2.313-5.281 0-3.861-3.135-7.024-7-6.969zM2 12h3v2h-3v-2zM27 12h3v2h-3v-2zM7.5 20.094l1.406 1.406-2.094 2.094-1.406-1.406zM24.5 20.094l2.094 2.094-1.406 1.406-2.094-2.094zM14 24v2h4v-2h-4z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,7 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M31.7,29.2L29.8,23V7.1c0-1.1-1-2.1-2.1-2.1H4.8c-1.1,0-2.1,1-2.1,2.1V23l-2,6.1c-0.2,0.6-0.1,1.3,0.2,1.9
c0.4,0.6,1,0.9,1.7,0.9h27.1c0.7,0,1.3-0.3,1.7-0.9C31.8,30.4,31.9,29.7,31.7,29.2z M4.2,7.1c0-0.3,0.3-0.6,0.6-0.6h22.8
c0.3,0,0.6,0.3,0.6,0.6v15.3h-24V7.1z M30.2,30.2c-0.1,0.1-0.2,0.2-0.5,0.2H2.6c-0.3,0-0.4-0.2-0.5-0.2C2.1,30,2,29.8,2.1,29.6
L4,23.9h24.4l1.8,5.8C30.4,29.8,30.3,30,30.2,30.2z"/>
<rect x="13.5" y="27.8" width="5.6" height="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 596 B

View File

@ -0,0 +1,13 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M7.5,4.9c0.1-0.1,0.2-0.2,0.3-0.2l0.7-0.6l-1-1.4L6.8,3.1C6.7,3.3,6.6,3.4,6.5,3.5L5.8,4.1l1,1.3L7.5,4.9z"/>
<path d="M31.5,19.2c0.2-1,0.3-2.1,0.3-3.2c0-8.6-7-15.8-15.8-15.8c-1.7,0-3.3,0.2-5,0.8l-0.8,0.2l0.6,1.6l0.8-0.2
c1.5-0.5,3-0.7,4.5-0.7c3.6,0,6.9,1.4,9.4,3.7C22.7,6,21.3,7.3,21,9.7c-0.2,1.3,0.6,2.4,1.4,3.3c0.3,0.5,1,1.3,0.9,1.5
c0,0.2-0.2,0.3-0.6,0.6c-0.8,0.6-2,1.3-1.7,3.6c0.2,1.6,0.9,2.8,2.2,3.1c0.2,0.1,0.6,0.1,0.8,0.1c1,0,2.1-0.6,2.9-1.5
c0.8-1,0.8-1,2-0.5c0.2,0.1,0.4,0.2,0.6,0.3c-1.8,5.7-7.1,9.8-13.4,9.8c-1.7,0-3.4-0.3-4.9-0.9l-0.1-0.1c-0.7-1.8,1-4.3,2.9-5.9
c2.1-1.8,2.9-4.6,2.1-7.1c-0.5-1.2-1.4-2.1-2.7-2.3C12,13.5,10.6,14,9.7,15c-1,1.3-1.8,2-3,2c-1.3-0.1-3.3-2-4.3-3.1l-0.2-0.3
c0.4-2.1,1.2-4,2.4-5.7L5,7.2l-1.4-1L3.1,6.8c-1.8,2.8-2.9,5.9-2.9,9.2c0,8.6,7.1,15.8,15.9,15.8c7.3,0,13.5-5.1,15.2-11.9
L31.5,19.2L31.5,19.2z M2,16.1c0,0,0-0.1,0-0.1c1.1,1.1,2.9,2.6,4.5,2.6c2.1,0.1,3.3-1.3,4.5-2.5c0.5-0.6,1.3-0.8,2.1-0.7
c0.3,0.1,1,0.3,1.3,1.2c0.6,1.8,0,4-1.5,5.3c-0.9,0.9-3.7,3.6-3.5,6.5C5,26,2,21.3,2,16.1z M29.6,18.4c-1.7-0.9-2.7-1-4.1,0.9
c-0.6,0.7-1.3,1-1.8,0.9s-0.8-0.7-0.9-1.6c-0.1-1.3,0.3-1.5,0.9-2c0.5-0.3,1-0.7,1.3-1.5c0.3-1.2-0.5-2.2-1.2-3.1
c-0.5-0.7-1.2-1.5-1-2.1c0.1-1,0.3-2.6,4.3-2.7c1.9,2.4,3.1,5.5,3.1,8.8c0,0.8-0.1,1.6-0.2,2.4C29.8,18.4,29.7,18.4,29.6,18.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,11 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M31.8,20c0-6.8-5.6-9.1-8.2-9.1h-3.2c-0.3-1.6-1.7-2.9-3.5-2.9h-2.2v1.1h-3.4v1.5h3.4v2.1h-3.4v1.5h3.4v0.9h2.2
c1.7,0,3.1-1.2,3.4-2.7h3.3c1.3,0,6.7,1.3,6.7,7.6c0,6.9-7.2,7.2-7.2,7.2h-0.8h-0.1V28c0,1.3-1.1,2.4-2.5,2.4
c-1.4,0-2.5-1.1-2.5-2.4v-0.8H8.6V28c0,1.3-1.1,2.4-2.4,2.4c-1.5,0-2.4-1.2-2.4-2.4v-0.8H1.8v-2.6c0-0.1,0-0.8,0.5-1
c0.5-0.1,1.3-0.4,2.2-0.7c0.5-0.2,1.1-0.4,1.2-0.4c0.4,0,0.8-0.3,1.4-1l0.1-0.1c0.1-0.1,0.5-0.7,0.9-1.1c0.8-0.9,1-1.2,1.1-1.4
c0.1-0.1,0.5-0.4,1-0.4h8.6c0.6,0,0.8,0.2,1,0.4c0.4,0.5,1.9,2.5,1.9,2.5l1.2-0.9c0,0-1.5-2-1.9-2.5c-0.5-0.7-1.2-1-2.2-1h-8.6
c-1,0-2,0.6-2.2,1.1c-0.1,0.1-0.6,0.8-1,1.2c-0.5,0.6-0.8,1-0.9,1.2c-0.2,0.2-0.5,0.4-0.6,0.6c-0.2,0-0.5,0.2-1.5,0.5
c-0.7,0.3-1.6,0.6-2,0.7c-1,0.3-1.7,1.2-1.7,2.4v4.1h2.2c0.3,1.8,1.9,3.2,3.8,3.2c1.9,0,3.5-1.4,3.9-3.2h5.7
c0.3,1.8,1.9,3.2,3.9,3.2c1.9,0,3.5-1.4,3.9-3.2C26.2,28.5,31.8,26.6,31.8,20z M16.9,13.6h-0.8V9.6h0.8c1.1,0,2.1,0.9,2.1,2
S18,13.6,16.9,13.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,19 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M16,9.9c-2.7,0-4.8-2.2-4.8-4.8s2.2-4.8,4.8-4.8c2.7,0,4.8,2.2,4.8,4.8S18.6,9.9,16,9.9z M16,1.8c-1.8,0-3.3,1.5-3.3,3.3
s1.5,3.3,3.3,3.3c1.8,0,3.3-1.5,3.3-3.3S17.8,1.8,16,1.8z"/>
<path d="M19.6,32c-3.3,0-5.4-0.1-6-0.3c-2.6-1.1-2.6-2.5-2.4-3.1c0.5-2,4.1-3.1,8.7-2.6l0.3,0c0.4,0,0.8,0.3,0.8,0.8
s-0.3,0.8-0.8,0.8h-0.3c-4.7-0.4-7,0.8-7.1,1.5c-0.1,0.3,0.4,0.9,1.5,1.4c0.8,0.3,7.5,0.2,10.1,0.2c0.7,0,1.3,0,1.7,0
c0.6,0,1.3-0.5,1.3-1.3c0-1.6-2-3.1-3.6-4.2c-0.5-0.3-0.9-0.7-1.3-1c-0.2-0.2-0.4-0.4-0.7-0.6c-0.9-0.8-1.9-1.5-2-2.5
c-0.1-0.5,0-1.1,0.2-1.8c0.2-0.6,0.3-1.2,0.3-2c0-0.4,0.3-0.7,0.6-0.7c0.4-0.1,0.7,0.1,0.8,0.5c0.9,2.3,1.4,2.7,2.7,3.9
c0.6,0.5,1.3,1.1,2.4,2.1c0.5,0.5,1.2,0.5,1.6,0c0.2-0.2,0.3-0.5,0.3-0.8s-0.1-0.5-0.3-0.8l-2.9-2.8c-1.3-1.2-1.9-2.8-2.5-4
c-0.7-1.7-1.1-2.5-2.1-2.5h-9c-0.9,0-1.3,0.7-2.1,2.5c-0.5,1.2-1.2,2.8-2.5,4l-2.9,2.8c-0.2,0.2-0.3,0.5-0.3,0.8
c0,0.3,0.1,0.5,0.3,0.8c0.5,0.5,1.2,0.5,1.6,0c0.5-0.5,1-1,1.6-1.5c1.6-1.3,3.2-2.7,3.5-4.4c0.1-0.4,0.4-0.6,0.8-0.6
c0.4,0,0.7,0.4,0.7,0.7c0,0.8,0.2,1.4,0.3,2c0.2,0.6,0.3,1.3,0.2,1.9c-0.2,0.9-1.2,1.7-2.1,2.5c-0.2,0.2-0.4,0.3-0.5,0.5
c-0.4,0.3-0.8,0.7-1.3,1c-1.6,1.2-3.6,2.7-3.6,4.2c0,0.6,0.5,1.3,1.3,1.3h4c0.4,0,0.8,0.3,0.8,0.8S11.3,32,10.9,32h-4
c-1.5,0-2.8-1.3-2.8-2.8c0-2.4,2.3-4.1,4.2-5.5c0.4-0.3,0.9-0.6,1.2-0.9c0.2-0.1,0.3-0.3,0.5-0.5c0.7-0.6,1.5-1.3,1.6-1.7
c0.1-0.2,0-0.6-0.1-1c-0.8,1.1-1.9,2-2.9,2.9c-0.6,0.5-1.1,0.9-1.5,1.4c-1.1,1.1-2.7,1.1-3.7,0c-0.5-0.5-0.8-1.2-0.8-1.8
s0.3-1.3,0.8-1.8l2.9-2.8c1.1-1,1.6-2.3,2.1-3.5c0.7-1.7,1.4-3.4,3.5-3.4h9c2,0,2.7,1.7,3.5,3.4c0.5,1.2,1.1,2.5,2.1,3.5l2.9,2.8
c0.5,0.5,0.8,1.2,0.8,1.8s-0.3,1.3-0.8,1.8c-1.1,1.1-2.7,1-3.7,0c-1-0.9-1.7-1.5-2.3-2c-0.9-0.8-1.5-1.3-2.1-2.3
c-0.1,0.4-0.2,0.8-0.2,1c0.1,0.5,0.9,1.1,1.5,1.6c0.2,0.2,0.5,0.4,0.7,0.6c0.3,0.3,0.8,0.6,1.2,0.9c1.9,1.4,4.2,3.1,4.2,5.5
c0,1.5-1.3,2.8-2.8,2.8c-0.3,0-0.9,0-1.7,0C22.5,32,20.9,32,19.6,32z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>music</title>
<path d="M27 3.781v17.219c0 2.197-1.803 4-4 4s-4-1.803-4-4 1.803-4 4-4c0.732 0 1.407 0.214 2 0.563v-7.375l-14 2.625v11.188c0 2.197-1.803 4-4 4s-4-1.803-4-4 1.803-4 4-4c0.732 0 1.407 0.214 2 0.563v-13.406l0.813-0.125 16-3zM25 6.188l-14 2.625v2l14-2.625v-2zM23 19c-1.116 0-2 0.884-2 2s0.884 2 2 2 2-0.884 2-2-0.884-2-2-2zM7 22c-1.116 0-2 0.884-2 2s0.884 2 2 2 2-0.884 2-2-0.884-2-2-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@ -0,0 +1,18 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M31,20.4c-0.4-0.3-1-0.5-1.6-0.5l-0.2-0.4c-0.5-1.4-2.1-1.6-3.6-1.1c-0.2,0.1-0.6,0.3-1,0.6c-0.7-0.8-2-0.9-3.2-0.5
c-0.5,0.2-1.8,0.9-2.5,1.3c-2.6-0.5-5.8-1.1-6.5-1.2c-2-0.2-3.3-0.2-4.9,0.3c-0.6,0.2-5.2,2.3-6.1,2.7L0.9,22l0.6,1.4l0.7-0.3
c2.5-1.2,5.5-2.5,5.9-2.6c1.4-0.4,2.4-0.4,4.3-0.2c0.9,0.1,8.1,1.5,9.4,1.8c0.9,0.2,0.9,0.7,0.9,0.8c0,0.3-0.1,0.4-0.1,0.4
s0,0-0.1,0h-8.6v1.5h2l0.5,0.3c0.1,0,1.4,0.9,2.4,1.3c1,0.4,2,0.4,2.7-0.2c2.6-1.7,6.7-4.4,7-4.6c0.6-0.4,1.4-0.4,1.6-0.2
c0.1,0.1,0.2,0.2,0.2,0.3s-0.1,0.2-0.2,0.3c-2.8,2-10.3,7.5-11.2,8c-1.2,0.7-2.8,0.3-3.6,0.1c-1.2-0.4-7.1-2.5-7.6-2.7
c-0.7-0.2-2.4-0.6-3.9,0C2.7,28,1,29,0.9,29l-0.7,0.3l0.7,1.3l0.7-0.3c0,0,1.7-0.9,2.8-1.4c0.9-0.4,2.2-0.2,2.6,0
c0.5,0.2,6.4,2.3,7.7,2.7c0.5,0.1,1.3,0.3,2.2,0.3c0.8,0,1.7-0.2,2.5-0.3c1.1-0.6,10.9-7.8,11.3-8.1c0.5-0.4,0.8-0.9,0.8-1.5
S31.5,20.8,31,20.4z M22,20c0,0,0.7-0.2,1.2-0.1c-0.4,0.3-0.8,0.5-1.1,0.7c0,0,0,0,0,0c-0.2-0.1-0.5-0.1-0.9-0.2
C21.6,20.2,21.9,20.1,22,20z M19.3,25.2c-0.2-0.1-0.4-0.2-0.6-0.3h2.1c-0.1,0.1-0.2,0.2-0.3,0.2C20.3,25.3,19.9,25.5,19.3,25.2z
M24.2,22.7c0-0.5-0.2-0.9-0.5-1.2c1.2-0.7,2.4-1.5,2.7-1.6c0,0,1.4-0.4,1.7,0.3l0,0.1c-0.2,0.1-0.3,0.1-0.5,0.2
C27.5,20.5,25.8,21.6,24.2,22.7z"/>
<path d="M15.9,16c3,0,4.5-1.3,5.3-2.3c1.3,1.4,1.5,3,1.5,3.1l0.1,0.7l1.6-0.1l-0.1-0.7c-0.1-0.1-0.3-2.5-2.5-4.4
c0.4-1.6,0.1-3.5-1-4.9c-2.2-2.7-5.4-2.9-8.6-2.8c-2.9,0-4.6,0-6.4-0.9L4.9,3.2L4.7,4.2C4.3,6.3,5.2,9,7.1,11.5
C9.4,14.4,12.7,16,15.9,16z M12.3,6.1c3-0.1,5.7,0,7.4,2.2c0.7,0.8,1,2,0.8,3c-2-1.1-4-1.6-5.6-2.1c-1.2-0.3-2.3-0.6-2.7-1
l-0.6-0.5l-1,1.1l0.6,0.5c0.7,0.6,1.9,0.9,3.4,1.3c1.6,0.4,3.6,1,5.5,2.1c-0.7,1.2-2.1,1.8-4.1,1.8c-2.8,0-5.6-1.4-7.6-4
C7,8.9,6.2,7,6.2,5.5C8,6.1,9.7,6.1,12.3,6.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,22 @@
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M31.6,16c-0.6-6.4-1.9-6.4-2.4-6.4c-0.4,0-0.8,0.1-1.1,0.4c-0.8,0.8-0.8,2.5-0.8,4.2c0,0.5,0,1.2,0,1.7h-0.1
C26,15.5,25.1,17,24.5,18c-0.1,0.2-0.2,0.4-0.3,0.5c-0.8,0.9-1.2,1.2-2.2,2c-0.4,0.4-1,0.6-1.5,0.9c-0.6,0.3-1.2,0.6-1.8,1.1
c-2.3,1.9-1.8,4.1-1.4,6.1c0.2,0.8,0.3,1.5,0.3,2.3v0.8h1.5v-0.8c0-0.9-0.2-1.7-0.4-2.6c-0.4-2.1-0.6-3.4,0.9-4.6
c0.4-0.4,1-0.6,1.5-0.9c0.6-0.3,1.2-0.6,1.8-1.1c1-0.8,1.5-1.2,2.4-2.2c0.1-0.1,0.3-0.4,0.4-0.7c0.2-0.3,0.7-1.2,1-1.4
c0.3,0.2,0.4,1.3,0.3,1.7c-0.3,1-1.1,1.9-1.9,2.8c-0.7,0.8-1.4,1.6-1.8,2.5L23,25l1.3,0.7l0.3-0.7c0.4-0.7,1-1.4,1.6-2.2
c0.9-1,1.8-2.1,2.2-3.3c0.2-0.5,0.2-1.4-0.1-2.2c0.4-0.5,0.4-1.4,0.4-3c0-0.8,0-2.5,0.3-3c0.3,0.5,0.7,1.8,1,4.9
c0.2,2,0.2,4.8-1.2,7.1c-0.5,0.9-1.3,1.7-2,2.4c-1.3,1.4-2.7,2.9-3,5.2l-0.1,0.7l1.5,0.2l0.1-0.7c0.2-1.7,1.4-3,2.6-4.3
c0.8-0.8,1.5-1.7,2.2-2.7C31.9,21.4,31.9,18.2,31.6,16z"/>
<path d="M26.3,7.2c0-2.9-2.4-5.2-5.2-5.2c-2.4,0-3.9,1.4-4.5,2c-0.6-0.6-2.1-2-4.5-2C9.3,2,6.9,4.3,6.9,7.1c0,1,0.5,1.9,1,2.4
c0.4,0.6,0.8,1,0.8,1l7.4,7.5l0.6,0.6l7.9-7.9C24.6,10.7,26.3,9.2,26.3,7.2z M16.6,16.7l-7-7c0,0-0.4-0.3-0.7-0.8
C8.6,8.4,8.3,7.7,8.3,7.2c0-2.1,1.7-3.8,3.8-3.8c2,0,3.9,2,3.9,2L16.6,6l0.5-0.6c0,0,1.9-2,4-2s3.8,1.7,3.8,3.8
c0,1.1-1.3,2.5-1.3,2.5L16.6,16.7z"/>
<path d="M13.4,22.6c-0.6-0.4-1.2-0.8-1.8-1.1c-0.6-0.3-1.1-0.6-1.5-0.9c-1-0.8-1.4-1.2-2.2-2c-0.1-0.1-0.2-0.3-0.3-0.5
c-0.6-1-1.5-2.5-2.7-2.1c-0.1,0-0.1,0.1-0.2,0.1c0-0.5,0-1.2,0-1.7c0-1.8,0-3.5-0.8-4.2c-0.3-0.3-0.7-0.4-1-0.4
c-0.5,0-1.8,0-2.4,6.4c-0.3,2.2-0.2,5.4,1.4,8.1c0.6,1,1.4,1.9,2.2,2.7c1.3,1.3,2.4,2.6,2.7,4.3L6.9,32l1.5-0.2L8.1,31
c-0.3-2.2-1.7-3.7-3.1-5.1c-0.7-0.8-1.5-1.6-2-2.5c-1.4-2.3-1.4-5.2-1.2-7.1c0.3-3.1,0.8-4.5,1-4.9c0.3,0.5,0.3,2.2,0.3,3
c0,1.7,0,2.6,0.5,3.1c-0.2,0.8-0.2,1.7-0.1,2.1C4,20.9,4.9,22,5.8,23c0.6,0.7,1.3,1.4,1.6,2.2l0.3,0.7L9,25.2l-0.3-0.7
c-0.5-0.9-1.2-1.7-1.8-2.5c-0.8-0.9-1.6-1.8-1.9-2.8c-0.1-0.4,0-1.5,0.3-1.7c0.3,0.2,0.8,1.1,1,1.4s0.3,0.6,0.5,0.7
c0.8,0.9,1.4,1.4,2.4,2.2c0.6,0.4,1.2,0.8,1.8,1.1c0.6,0.3,1.1,0.6,1.5,0.9c1.5,1.3,1.3,2.6,0.9,4.6C13.1,29.3,13,30.1,13,31v0.8
h1.5V31c0-0.7,0.2-1.5,0.3-2.3C15.2,26.8,15.7,24.5,13.4,22.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,8 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="32px" height="32px">
<path d="M27.3,5c-1.5-1.5-3.1-2.6-5-3.4c-2-0.8-4-1.2-6.2-1.2c-2.1,0-4.2,0.4-6.2,1.2C8,2.4,6.3,3.5,4.9,5c-1.5,1.5-2.6,3.1-3.4,5
c-0.8,2-1.2,4-1.2,6.2c0,2.1,0.4,4.2,1.2,6.2c0.8,1.9,1.9,3.6,3.4,5c1.5,1.5,3.1,2.6,5,3.4c2,0.8,4,1.2,6.2,1.2
c2.1,0,4.2-0.4,6.2-1.2c1.9-0.8,3.6-1.9,5-3.4c1.5-1.5,2.6-3.1,3.4-5c0.8-2,1.2-4,1.2-6.2c0-2.1-0.4-4.2-1.2-6.2
C29.8,8.1,28.7,6.4,27.3,5z M15.1,2.4v13.4L5.4,25c-2-2.4-3.2-5.5-3.2-8.8C2.2,8.9,7.9,2.9,15.1,2.4z M6.8,26.4l8.3-7.9V30
C11.9,29.7,9,28.4,6.8,26.4z M17.1,30V18.5l8.3,7.9C23.1,28.4,20.2,29.7,17.1,30z M26.7,25l-9.7-9.2V2.4c7.2,0.5,12.8,6.5,12.8,13.8
C29.9,19.5,28.7,22.6,26.7,25z"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@ -0,0 +1,12 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M19.3,15.4c0.3,0.2,0.9,0.5,1.1,0.8c0.5,0.6,0.6,1.2,0.6,1.3l0.8,3.1l1.4-0.4l-0.8-3c0-0.1-0.2-0.9-0.9-1.8
c-0.5-0.6-1.5-1.1-1.6-1.2l-8.5-4.7l-0.7,1.3L19.3,15.4z"/>
<path d="M7.4,19.1c0.4,2.2,2.5,4.1,3.4,4.8l0.9-1.2c-1.7-1.4-2.8-2.9-2.9-4.2l0-0.4l-2.3-1.3L11,7.8c0.1-0.2,0.1-0.4,0-0.6
c-0.1-0.2-0.2-0.3-0.4-0.4L6.5,4.9L5.9,6.3l3.4,1.6L4.4,17.9l-3.4-1.6l-0.6,1.4l4.1,1.9c0.1,0,0.2,0.1,0.3,0.1
c0.3,0,0.5-0.2,0.7-0.4l0.5-1L7.4,19.1z"/>
<path d="M28.9,30.5l-2.8-10.1l-5.6,1.5l-1.1-4c-0.1-0.4-0.5-0.6-0.9-0.5c-0.4,0.1-0.6,0.5-0.5,0.9l2.1,7.6c0.2,0.7-0.2,1.4-1,1.6
c-0.3,0.1-0.7,0-0.9-0.1c-0.3-0.2-0.5-0.4-0.6-0.8l-1.4-5c-0.1-0.3-0.4-0.5-0.7-0.6c-0.1,0-2.4,0-3.8-3.5c-0.2-0.4-0.6-0.6-1-0.4
c-0.4,0.2-0.6,0.6-0.4,1c1.4,3.4,3.6,4.2,4.6,4.4l0.2,0.9l-6.8,1.9l1.2,4.4l1.4-0.4l-0.8-2.9l5.3-1.5l0.6,2.1
c0.2,0.7,0.7,1.3,1.3,1.7c0.4,0.2,0.9,0.4,1.3,0.4c0.3,0,0.5,0,0.8-0.1c1.5-0.4,2.3-1.9,1.9-3.4L21,23.4l4.1-1.1l2.2,8.2h-20V32
h24.5v-1.5H28.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,8 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path class="st0" d="M30.8,12C30.5,7.4,27.9,3.5,24,1.8c-2-0.9-4.3-1.4-6.4-1.4v0.1c0,0,0,0,0,0V0.4c-2,0-3.9,0.4-5.7,1.2
c-1.8,0.8-3.4,2-4.7,3.6c0,0,0,0,0,0c-0.1,0.1-2.6,3.3-2.5,8.5l-3.4,6.3C1,20.7,1,21.4,1.4,22c0.3,0.6,1,0.9,1.6,0.9h1.9v2.8
c0,1.7,1.4,3.1,3.1,3.1h2.8v1.7c0,0.7,0,1.3,0,1.3l1.4,0v-4.3h-4c-1,0-1.9-0.8-1.9-1.9v-4H3.2c-0.5,0-0.8-0.5-0.6-1L6.2,14
c-0.2-5,2.2-8,2.2-8c1.2-1.4,2.6-2.5,4.2-3.2c1.5-0.6,3.2-1,4.8-1c2,0,4,0.4,5.9,1.3c3.7,1.6,5.7,5.3,6,9.2c0.1,2.1-0.3,4.3-1.1,6.2
c-0.4,1-0.9,1.9-1.5,2.8c-0.3,0.5-1.8,1.9-1.8,2.4c0,0,0,6,0,6.8l0,1.3c0.2,0,1.5,0,1.5,0s0-0.8,0-1.3c0,0,0-5.4,0-6.6
c0.2-0.3,0.6-0.7,0.9-1c0.4-0.4,0.6-0.7,0.8-0.9c0.7-1,1.2-2,1.6-3.1C30.5,16.6,30.9,14.3,30.8,12z"/>
</svg>

After

Width:  |  Height:  |  Size: 823 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>save</title>
<path d="M5 5h17.406l0.313 0.281 4 4 0.281 0.313v17.406h-22v-22zM7 7v18h2v-9h14v9h2v-14.563l-3-3v5.563h-12v-6h-3zM12 7v4h8v-4h-2v2h-2v-2h-4zM11 18v7h10v-7h-10z"></path>
</svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -0,0 +1,25 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M28.4,16.1c1-1.7,1.9-3.3,2.5-4.9c1.5-4,1.3-7.3-0.6-9.2C27.5-0.8,22,0.1,16.1,3.7C10.3,0.1,4.7-0.8,2,1.9
c-2.8,2.8-1.9,8.3,1.8,14.1c-3.3,5.3-4.4,10.4-2.5,13.4C1.5,29.7,1.7,30,2,30.2c1.1,1.1,2.6,1.6,4.4,1.6c1.4,0,3-0.3,4.8-1
c1.6-0.6,3.2-1.4,4.9-2.5c2.6,1.6,5.1,2.7,7.4,3.2c0,0,0.1,0,0.1,0c0.3,0.1,0.7,0.1,1,0.2c0.1,0,0.1,0,0.2,0c0.4,0,0.7,0.1,1,0.1
c0,0,0,0,0,0c0,0,0,0,0,0c0.3,0,0.5,0,0.8-0.1c0.2,0,0.4,0,0.5,0c0.3,0,0.6-0.1,0.9-0.2c0.1,0,0.2,0,0.3-0.1
c0.3-0.1,0.5-0.2,0.8-0.4c0.1,0,0.2-0.1,0.3-0.1c0.3-0.2,0.6-0.4,0.9-0.7c1.9-1.9,2.1-5.2,0.6-9.2C30.3,19.4,29.5,17.7,28.4,16.1z
M29.5,21.5c1.3,3.4,1.1,6.2-0.3,7.6c-0.2,0.2-0.4,0.3-0.6,0.5c-0.2,0.1-0.4,0.2-0.6,0.3c0,0-0.1,0-0.1,0.1
c-0.3,0.1-0.5,0.2-0.8,0.2c0,0,0,0,0,0c-0.3,0.1-0.6,0.1-0.9,0.1c0,0,0,0,0,0c-0.3,0-0.6,0-0.8,0c-0.1,0-0.1,0-0.2,0
c-0.2,0-0.5-0.1-0.7-0.1c-0.2,0-0.3,0-0.5-0.1c-0.1,0-0.3-0.1-0.4-0.1c-0.3-0.1-0.6-0.1-0.9-0.2c0,0-0.1,0-0.1,0
c-1.5-0.5-3.2-1.3-4.9-2.3c1.9-1.3,3.7-2.8,5.4-4.6c1.7-1.7,3.3-3.6,4.6-5.5C28.3,18.8,29,20.2,29.5,21.5z M5.6,16.1
c1.3-1.9,2.9-3.9,4.7-5.8c1.9-1.9,3.8-3.5,5.8-4.7c0,0,0,0,0.1,0c0.1,0.1,0.2,0.2,0.4,0.2c0.4,0.3,0.8,0.6,1.2,0.8c0,0,0,0,0.1,0.1
c0.4,0.3,0.8,0.6,1.2,1c0.1,0.1,0.2,0.2,0.4,0.3c0.3,0.3,0.6,0.5,0.9,0.8c0.1,0.1,0.2,0.2,0.4,0.3c0.4,0.4,0.8,0.8,1.2,1.2
c1.8,1.8,3.5,3.8,4.8,5.8c-1.3,2-2.9,3.9-4.8,5.8c-1.8,1.8-3.8,3.4-5.8,4.8c-1.9-1.3-3.9-2.9-5.8-4.8C8.5,20,6.9,18,5.6,16.1z
M25.8,1.8c1.4,0,2.6,0.4,3.4,1.2c1.4,1.4,1.5,4.2,0.3,7.6c-0.5,1.3-1.2,2.7-2,4c-1.3-1.9-2.8-3.7-4.6-5.5
c-1.8-1.8-3.6-3.3-5.4-4.6C20.6,2.8,23.5,1.8,25.8,1.8z M3,3c0.2-0.2,0.4-0.4,0.7-0.5C3.8,2.4,3.9,2.4,4,2.3
c0.1-0.1,0.3-0.1,0.4-0.2C4.6,2.1,4.8,2,4.9,2c0.1,0,0.3-0.1,0.4-0.1c0.2,0,0.4,0,0.5,0c0.1,0,0.3,0,0.4,0c0.3,0,0.6,0,1,0.1
c0.1,0,0.1,0,0.2,0C7.8,1.9,8.1,2,8.4,2c0.1,0,0.2,0,0.3,0.1c0.3,0.1,0.7,0.2,1,0.3c0,0,0.1,0,0.1,0c0.4,0.1,0.8,0.3,1.2,0.4
c0.1,0,0.2,0.1,0.3,0.1c0.3,0.1,0.6,0.3,0.9,0.4c0.1,0,0.2,0.1,0.3,0.1c0.4,0.2,0.8,0.4,1.1,0.6c0,0,0.1,0,0.1,0
c0.4,0.2,0.7,0.4,1.1,0.6c-1.8,1.3-3.7,2.8-5.4,4.6C7.5,11,6,12.8,4.7,14.7C1.8,9.7,0.9,5.1,3,3z M10.7,29.4
c-3.4,1.3-6.2,1.1-7.6-0.3C0.9,27,1.8,22.4,4.7,17.5c1.3,1.8,2.8,3.7,4.6,5.4c0.4,0.4,0.9,0.8,1.3,1.2c0.1,0.1,0.2,0.2,0.3,0.3
c0.4,0.3,0.8,0.7,1.1,1c0.1,0.1,0.1,0.1,0.2,0.2c0.8,0.7,1.7,1.3,2.5,1.9C13.4,28.3,12,28.9,10.7,29.4z"/>
<path d="M19.4,19.3c1.8-1.8,1.8-4.8,0-6.6c-1.8-1.8-4.8-1.8-6.6,0c-1.8,1.8-1.8,4.8,0,6.6c0.9,0.9,2.1,1.4,3.3,1.4
C17.3,20.7,18.5,20.3,19.4,19.3z M13.9,13.8c0.6-0.6,1.4-0.9,2.2-0.9s1.6,0.3,2.2,0.9c1.2,1.2,1.2,3.2,0,4.4s-3.2,1.2-4.4,0
S12.7,15.1,13.9,13.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,13 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32px" height="32px" viewBox="0 0 32 32">
<path d="M15.9,18.6c-1.4,0-2.7-0.5-3.7-1.5c-1.4-1.4-1.9-3.3-1.4-5.2c0.5-1.8,1.9-3.2,3.7-3.7c1.9-0.5,3.8,0,5.2,1.4
c1.3,1.3,1.9,3.3,1.4,5.2c-0.5,1.8-1.9,3.2-3.7,3.7c0,0,0,0,0,0C16.9,18.5,16.4,18.6,15.9,18.6z M17.2,17.7L17.2,17.7
L17.2,17.7z M15.9,9.5c-0.3,0-0.7,0-1,0.1c-1.2,0.3-2.3,1.4-2.6,2.6c-0.4,1.4,0,2.7,1,3.7s2.4,1.3,3.7,1
c1.2-0.3,2.3-1.4,2.6-2.6c0.4-1.4,0-2.7-1-3.7C17.9,9.9,16.9,9.5,15.9,9.5z"/>
<path d="M27.8,32H4v-2.2c0-5.5,4.4-9.9,9.9-9.9h4c5.5,0,9.9,4.4,9.9,9.9V32z M5.5,30.5h20.8v-0.7c0-4.6-3.8-8.4-8.4-8.4h-4
c-4.6,0-8.4,3.8-8.4,8.4V30.5z"/>
<rect x="15.6" y="0.5" width="1.5" height="4.3"/>
<rect x="23.6" y="7.2" transform="matrix(0.8679 -0.4968 0.4968 0.8679 -0.5339 13.83)" width="4.3" height="1.5"/>
<rect x="5.9" y="5.9" transform="matrix(0.4969 -0.8678 0.8678 0.4969 -3.6304 9.7958)" width="1.5" height="4.3"/>
<rect x="9.4" y="1.8" transform="matrix(0.8207 -0.5714 0.5714 0.8207 -0.4488 6.4807)" width="1.5" height="4.3"/>
<rect x="20.1" y="3.2" transform="matrix(0.5712 -0.8208 0.8208 0.5712 6.3227 19.9037)" width="4.3" height="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>suitcase</title>
<path d="M14 3h4c1.093 0 2 0.907 2 2v1h3v-1h2v1h2c1.093 0 2 0.907 2 2v16c0 1.093-0.907 2-2 2h-22c-1.093 0-2-0.907-2-2v-16c0-1.093 0.907-2 2-2h2v-1h2v1h3v-1c0-1.093 0.907-2 2-2zM14 5v1h4v-1h-4zM5 8v16h2v-15h2v15h14v-15h2v15h2v-16h-22z"></path>
</svg>

After

Width:  |  Height:  |  Size: 405 B

View File

@ -9,6 +9,11 @@
:disabled="isDisabled(category.id)" :disabled="isDisabled(category.id)"
:icon="category.icon" :icon="category.icon"
size="small" size="small"
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
> >
{{ $t(`contribution.category.name.${category.slug}`) }} {{ $t(`contribution.category.name.${category.slug}`) }}
</base-button> </base-button>

View File

@ -18,8 +18,7 @@ const comment = {
author: { author: {
id: '1', id: '1',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
slug: 'jenny-rostock', slug: 'jenny-rostock',
name: 'Rainer Unsinn', name: 'Rainer Unsinn',

View File

@ -202,7 +202,7 @@ describe('ContributionForm.vue', () => {
beforeEach(async () => { beforeEach(async () => {
jest.useFakeTimers() jest.useFakeTimers()
mocks.$apollo.mutate = jest.fn().mockRejectedValueOnce({ mocks.$apollo.mutate = jest.fn().mockRejectedValueOnce({
message: 'Not Authorised!', message: 'Not Authorized!',
}) })
wrapper = Wrapper() wrapper = Wrapper()
postTitleInput = wrapper.find('.ds-input') postTitleInput = wrapper.find('.ds-input')
@ -213,7 +213,7 @@ describe('ContributionForm.vue', () => {
it('shows an error toaster when apollo mutation rejects', async () => { it('shows an error toaster when apollo mutation rejects', async () => {
await wrapper.find('form').trigger('submit') await wrapper.find('form').trigger('submit')
await mocks.$apollo.mutate await mocks.$apollo.mutate
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!') await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!')
}) })
}) })
}) })

View File

@ -29,7 +29,7 @@ describe('DeleteData.vue', () => {
}, },
}, },
}) })
.mockRejectedValue({ message: 'Not authorised!' }), .mockRejectedValue({ message: 'Not Authorized!' }),
}, },
$toast: { $toast: {
error: jest.fn(), error: jest.fn(),
@ -180,7 +180,7 @@ describe('DeleteData.vue', () => {
// second submission causes mutation to reject // second submission causes mutation to reject
await deleteAccountBtn.trigger('click') await deleteAccountBtn.trigger('click')
await mocks.$apollo.mutate await mocks.$apollo.mutate
expect(mocks.$toast.error).toHaveBeenCalledWith('Not authorised!') expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!')
}) })
}) })
}) })

View File

@ -103,7 +103,7 @@ export default {
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: gql` mutation: gql`
mutation($id: ID!, $resource: [Deletable]) { mutation ($id: ID!, $resource: [Deletable]) {
DeleteUser(id: $id, resource: $resource) { DeleteUser(id: $id, resource: $resource) {
id id
} }

View File

@ -9,8 +9,7 @@ const embed = {
title: 'Video Titel', title: 'Video Titel',
// html: null, // html: null,
description: 'Video Description', description: 'Video Description',
html: html: '<iframe width="auto" height="250" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
'<iframe width="auto" height="250" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
} }
const plugins = [ const plugins = [

View File

@ -33,8 +33,7 @@ describe('Embed.vue', () => {
video: null, video: null,
lang: 'de', lang: 'de',
sources: ['resource', 'oembed'], sources: ['resource', 'oembed'],
html: html: '<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
'<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
}), }),
} }
}) })

View File

@ -114,8 +114,7 @@ describe('EmbedComponent.vue', () => {
video: null, video: null,
lang: 'de', lang: 'de',
sources: ['resource', 'oembed'], sources: ['resource', 'oembed'],
html: html: '<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>',
'<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>',
} }
wrapper = Wrapper() wrapper = Wrapper()
}) })

View File

@ -83,7 +83,7 @@ export default {
this.$apollo this.$apollo
.query({ .query({
query: gql` query: gql`
query($postId: ID!, $data: _EMOTEDInput!) { query ($postId: ID!, $data: _EMOTEDInput!) {
PostsEmotionsCountByEmotion(postId: $postId, data: $data) PostsEmotionsCountByEmotion(postId: $postId, data: $data)
} }
`, `,

View File

@ -15,8 +15,19 @@ describe('CategoriesFilter.vue', () => {
'posts/filteredCategoryIds': jest.fn(() => []), 'posts/filteredCategoryIds': jest.fn(() => []),
} }
const apolloMutationMock = jest.fn().mockResolvedValue({
data: { saveCategorySettings: true },
})
const mocks = { const mocks = {
$t: jest.fn((string) => string), $t: jest.fn((string) => string),
$apollo: {
mutate: apolloMutationMock,
},
$toast: {
success: jest.fn(),
error: jest.fn(),
},
} }
const Wrapper = () => { const Wrapper = () => {
@ -76,5 +87,14 @@ describe('CategoriesFilter.vue', () => {
expect(mutations['posts/RESET_CATEGORIES']).toHaveBeenCalledTimes(1) expect(mutations['posts/RESET_CATEGORIES']).toHaveBeenCalledTimes(1)
}) })
}) })
describe('save categories', () => {
it('calls the API', async () => {
wrapper = await Wrapper()
const saveButton = wrapper.findAll('.categories-filter .sidebar .base-button').at(1)
saveButton.trigger('click')
expect(apolloMutationMock).toBeCalled()
})
})
}) })
}) })

View File

@ -7,6 +7,8 @@
icon="check" icon="check"
@click="resetCategories" @click="resetCategories"
/> />
<hr />
<labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" />
</template> </template>
<template #filter-list> <template #filter-list>
<li v-for="category in categories" :key="category.id" class="item"> <li v-for="category in categories" :key="category.id" class="item">
@ -15,6 +17,11 @@
:filled="filteredCategoryIds.includes(category.id)" :filled="filteredCategoryIds.includes(category.id)"
:label="$t(`contribution.category.name.${category.slug}`)" :label="$t(`contribution.category.name.${category.slug}`)"
@click="toggleCategory(category.id)" @click="toggleCategory(category.id)"
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
delay: { show: 1500 },
}"
/> />
</li> </li>
</template> </template>
@ -24,6 +31,7 @@
<script> <script>
import { mapGetters, mapMutations } from 'vuex' import { mapGetters, mapMutations } from 'vuex'
import CategoryQuery from '~/graphql/CategoryQuery.js' import CategoryQuery from '~/graphql/CategoryQuery.js'
import SaveCategories from '~/graphql/SaveCategories.js'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection' import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton' import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
@ -47,6 +55,19 @@ export default {
resetCategories: 'posts/RESET_CATEGORIES', resetCategories: 'posts/RESET_CATEGORIES',
toggleCategory: 'posts/TOGGLE_CATEGORY', toggleCategory: 'posts/TOGGLE_CATEGORY',
}), }),
saveCategories() {
this.$apollo
.mutate({
mutation: SaveCategories(),
variables: { activeCategories: this.filteredCategoryIds },
})
.then(() => {
this.$toast.success(this.$t('filter-menu.save.success'))
})
.catch(() => {
this.$toast.error(this.$t('filter-menu.save.error'))
})
},
}, },
apollo: { apollo: {
Category: { Category: {

View File

@ -0,0 +1,58 @@
<template>
<dropdown ref="category-menu" placement="top-start" :offset="8" class="category-menu">
<base-button
slot="default"
:filled="filterActive"
:ghost="!filterActive"
slot-scope="{ toggleMenu }"
@click.prevent="toggleMenu()"
>
<ds-text uppercase>{{ $t('admin.categories.name') }}</ds-text>
</base-button>
<template slot="popover">
<div class="category-menu-options">
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
<categories-filter v-if="categoriesActive" />
</div>
</template>
</dropdown>
</template>
<script>
import Dropdown from '~/components/Dropdown'
import { mapGetters } from 'vuex'
import CategoriesFilter from './CategoriesFilter'
export default {
name: 'CategoriesMenu',
components: {
Dropdown,
CategoriesFilter,
},
props: {
placement: { type: String },
offset: { type: [String, Number] },
},
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
}
},
computed: {
...mapGetters({
filterActive: 'posts/isActive',
}),
},
}
</script>
<style lang="scss">
.category-menu-options {
max-width: $size-max-width-filter-menu;
padding: $space-small $space-x-small;
> .title {
font-size: $font-size-large;
}
}
</style>

View File

@ -8,6 +8,9 @@ let wrapper
describe('FilterMenu.vue', () => { describe('FilterMenu.vue', () => {
const mocks = { const mocks = {
$t: jest.fn((string) => string), $t: jest.fn((string) => string),
$env: {
CATEGORIES_ACTIVE: true,
},
} }
const getters = { const getters = {

View File

@ -82,7 +82,7 @@ export default {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation($id: ID!, $locale: String) { mutation ($id: ID!, $locale: String) {
UpdateUser(id: $id, locale: $locale) { UpdateUser(id: $id, locale: $locale) {
id id
locale locale

View File

@ -12,6 +12,8 @@ config.stubs['nuxt-link'] = '<span><slot /></span>'
config.stubs['locale-switch'] = '<span><slot /></span>' config.stubs['locale-switch'] = '<span><slot /></span>'
config.stubs['client-only'] = '<span><slot /></span>' config.stubs['client-only'] = '<span><slot /></span>'
const authUserMock = jest.fn().mockReturnValue({ activeCategories: [] })
describe('LoginForm', () => { describe('LoginForm', () => {
let mocks let mocks
let propsData let propsData
@ -26,10 +28,15 @@ describe('LoginForm', () => {
storeMocks = { storeMocks = {
getters: { getters: {
'auth/pending': () => false, 'auth/pending': () => false,
'auth/user': authUserMock,
}, },
actions: { actions: {
'auth/login': jest.fn(), 'auth/login': jest.fn(),
}, },
mutations: {
'posts/TOGGLE_CATEGORY': jest.fn(),
'posts/RESET_CATEGORIES': jest.fn(),
},
} }
const store = new Vuex.Store(storeMocks) const store = new Vuex.Store(storeMocks)
mocks = { mocks = {
@ -43,20 +50,46 @@ describe('LoginForm', () => {
} }
describe('fill in email and password and submit', () => { describe('fill in email and password and submit', () => {
const fillIn = (wrapper, opts = {}) => { const fillIn = async (wrapper, opts = {}) => {
const { email = 'email@example.org', password = '1234' } = opts const { email = 'email@example.org', password = '1234' } = opts
wrapper.find('input[name="email"]').setValue(email) wrapper.find('input[name="email"]').setValue(email)
wrapper.find('input[name="password"]').setValue(password) wrapper.find('input[name="password"]').setValue(password)
wrapper.find('form').trigger('submit') await wrapper.find('form').trigger('submit')
} }
it('dispatches login with form data', () => { it('dispatches login with form data', async () => {
fillIn(Wrapper()) await fillIn(Wrapper())
expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(expect.any(Object), { expect(storeMocks.actions['auth/login']).toHaveBeenCalledWith(expect.any(Object), {
email: 'email@example.org', email: 'email@example.org',
password: '1234', password: '1234',
}) })
}) })
describe('setting saved categories', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe('no categories saved', () => {
it('resets the categories', async () => {
await fillIn(Wrapper())
expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toBeCalled()
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).not.toBeCalled()
})
})
describe('categories saved', () => {
it('sets the categories', async () => {
authUserMock.mockReturnValue({ activeCategories: ['cat1', 'cat9', 'cat12'] })
await fillIn(Wrapper())
expect(storeMocks.mutations['posts/RESET_CATEGORIES']).toBeCalled()
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledTimes(3)
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat1')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat9')
expect(storeMocks.mutations['posts/TOGGLE_CATEGORY']).toBeCalledWith({}, 'cat12')
})
})
})
}) })
describe('Visibility of password', () => { describe('Visibility of password', () => {

View File

@ -58,6 +58,7 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch' import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo' import Logo from '~/components/Logo/Logo'
import ShowPassword from '../ShowPassword/ShowPassword.vue' import ShowPassword from '../ShowPassword/ShowPassword.vue'
import { mapGetters, mapMutations } from 'vuex'
export default { export default {
components: { components: {
@ -84,12 +85,27 @@ export default {
iconName() { iconName() {
return this.showPassword ? 'eye-slash' : 'eye' return this.showPassword ? 'eye-slash' : 'eye'
}, },
...mapGetters({
currentUser: 'auth/user',
}),
}, },
methods: { methods: {
...mapMutations({
toggleCategory: 'posts/TOGGLE_CATEGORY',
resetCategories: 'posts/RESET_CATEGORIES',
}),
async onSubmit() { async onSubmit() {
const { email, password } = this.form const { email, password } = this.form
try { try {
await this.$store.dispatch('auth/login', { email, password }) await this.$store.dispatch('auth/login', { email, password })
if (this.currentUser && this.currentUser.activeCategories) {
this.resetCategories()
if (this.currentUser.activeCategories.length > 0) {
this.currentUser.activeCategories.forEach((categoryId) => {
this.toggleCategory(categoryId)
})
}
}
this.$toast.success(this.$t('login.success')) this.$toast.success(this.$t('login.success'))
this.$emit('success') this.$emit('success')
} catch (err) { } catch (err) {

View File

@ -127,7 +127,7 @@ export default {
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: gql` mutation: gql`
mutation($id: ID!, $resource: [Deletable]) { mutation ($id: ID!, $resource: [Deletable]) {
DeleteUser(id: $id, resource: $resource) { DeleteUser(id: $id, resource: $resource) {
id id
} }

View File

@ -25,7 +25,7 @@ describe('DisableModal.vue', () => {
$t: jest.fn(), $t: jest.fn(),
$apollo: { $apollo: {
mutate: jest.fn().mockResolvedValueOnce().mockRejectedValue({ mutate: jest.fn().mockResolvedValueOnce().mockRejectedValue({
message: 'Not Authorised!', message: 'Not Authorized!',
}), }),
}, },
location: { location: {
@ -184,7 +184,7 @@ describe('DisableModal.vue', () => {
}) })
it('shows an error toaster when mutation rejects', async () => { it('shows an error toaster when mutation rejects', async () => {
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!') await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!')
}) })
}) })
}) })

View File

@ -53,7 +53,7 @@ export default {
// await this.modalData.buttons.confirm.callback() // await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) { mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) { review(resourceId: $resourceId, disable: $disable, closed: $closed) {
disable disable
} }

View File

@ -65,7 +65,7 @@ export default {
async handleSubmit(data) { async handleSubmit(data) {
this.loading = true this.loading = true
const mutation = gql` const mutation = gql`
mutation($oldPassword: String!, $password: String!) { mutation ($oldPassword: String!, $password: String!) {
changePassword(oldPassword: $oldPassword, newPassword: $password) changePassword(oldPassword: $oldPassword, newPassword: $password)
} }
` `

View File

@ -93,7 +93,7 @@ export default {
methods: { methods: {
async handleSubmitPassword() { async handleSubmitPassword() {
const mutation = gql` const mutation = gql`
mutation($nonce: String!, $email: String!, $password: String!) { mutation ($nonce: String!, $email: String!, $password: String!) {
resetPassword(nonce: $nonce, email: $email, newPassword: $password) resetPassword(nonce: $nonce, email: $email, newPassword: $password)
} }
` `

View File

@ -85,7 +85,7 @@ export default {
}, },
async handleSubmit() { async handleSubmit() {
const mutation = gql` const mutation = gql`
mutation($email: String!) { mutation ($email: String!) {
requestPasswordReset(email: $email) requestPasswordReset(email: $email)
} }
` `

View File

@ -17,8 +17,7 @@ export const post = {
author: { author: {
id: 'u3', id: 'u3',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
slug: 'jenny-rostock', slug: 'jenny-rostock',
name: 'Rainer Unsinn', name: 'Rainer Unsinn',

View File

@ -33,7 +33,7 @@
v-tooltip="{ v-tooltip="{
content: $t(`contribution.category.name.${category.slug}`), content: $t(`contribution.category.name.${category.slug}`),
placement: 'bottom-start', placement: 'bottom-start',
delay: { show: 500 }, delay: { show: 1500 },
}" }"
:icon="category.icon" :icon="category.icon"
/> />

View File

@ -35,7 +35,7 @@ import normalizeEmail from '~/components/utils/NormalizeEmail'
import translateErrorMessage from '~/components/utils/TranslateErrorMessage' import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
export const SignupMutation = gql` export const SignupMutation = gql`
mutation($email: String!, $inviteCode: String) { mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) { Signup(email: $email, inviteCode: $inviteCode) {
email email
} }
@ -165,9 +165,8 @@ export default {
}) })
this.setButtonValues() this.setButtonValues()
const { email: responseEmail } = this.sliderData.sliders[ const { email: responseEmail } =
this.sliderIndex this.sliderData.sliders[this.sliderIndex].data.response.Signup
].data.response.Signup
this.$toast.success( this.$toast.success(
this.$t('components.registration.email.form.success', { email: responseEmail }), this.$t('components.registration.email.form.success', { email: responseEmail }),
) )

View File

@ -25,7 +25,7 @@ import gql from 'graphql-tag'
import CONSTANTS_REGISTRATION from './../../constants/registration' import CONSTANTS_REGISTRATION from './../../constants/registration'
export const isValidInviteCodeQuery = gql` export const isValidInviteCodeQuery = gql`
query($code: ID!) { query ($code: ID!) {
isValidInviteCode(code: $code) isValidInviteCode(code: $code)
} }
` `

View File

@ -30,7 +30,7 @@ import CONSTANTS_REGISTRATION from './../../constants/registration'
import EmailDisplayAndVerify from './EmailDisplayAndVerify' import EmailDisplayAndVerify from './EmailDisplayAndVerify'
export const verifyNonceQuery = gql` export const verifyNonceQuery = gql`
query($email: String!, $nonce: String!) { query ($email: String!, $nonce: String!) {
VerifyNonce(email: $email, nonce: $nonce) VerifyNonce(email: $email, nonce: $nonce)
} }
` `

View File

@ -70,7 +70,7 @@ import { SweetalertIcon } from 'vue-sweetalert-icons'
import translateErrorMessage from '~/components/utils/TranslateErrorMessage' import translateErrorMessage from '~/components/utils/TranslateErrorMessage'
export const SignupMutation = gql` export const SignupMutation = gql`
mutation($email: String!, $inviteCode: String) { mutation ($email: String!, $inviteCode: String) {
Signup(email: $email, inviteCode: $inviteCode) { Signup(email: $email, inviteCode: $inviteCode) {
email email
} }

View File

@ -25,7 +25,7 @@ describe('ReleaseModal.vue', () => {
}, },
$t: jest.fn(), $t: jest.fn(),
$apollo: { $apollo: {
mutate: jest.fn().mockResolvedValueOnce().mockRejectedValue({ message: 'Not Authorised!' }), mutate: jest.fn().mockResolvedValueOnce().mockRejectedValue({ message: 'Not Authorized!' }),
}, },
location: { location: {
reload: jest.fn(), reload: jest.fn(),
@ -181,7 +181,7 @@ describe('ReleaseModal.vue', () => {
}) })
it('shows an error toaster when mutation rejects', async () => { it('shows an error toaster when mutation rejects', async () => {
await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorised!') await expect(mocks.$toast.error).toHaveBeenCalledWith('Not Authorized!')
}) })
}) })
}) })

View File

@ -52,7 +52,7 @@ export default {
// await this.modalData.buttons.confirm.callback() // await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) { mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) { review(resourceId: $resourceId, disable: $disable, closed: $closed) {
disable disable
} }

View File

@ -78,8 +78,7 @@ export const searchResults = [
id: 'u1', id: 'u1',
__typename: 'User', __typename: 'User',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
name: 'Peter Lustig', name: 'Peter Lustig',
slug: 'peter-lustig', slug: 'peter-lustig',
@ -88,8 +87,7 @@ export const searchResults = [
id: 'cdbca762-0632-4564-b646-415a0c42d8b8', id: 'cdbca762-0632-4564-b646-415a0c42d8b8',
__typename: 'User', __typename: 'User',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
name: 'Herbert Schultz', name: 'Herbert Schultz',
slug: 'herbert-schultz', slug: 'herbert-schultz',
@ -98,8 +96,7 @@ export const searchResults = [
id: 'u2', id: 'u2',
__typename: 'User', __typename: 'User',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
name: 'Bob der Baumeister', name: 'Bob der Baumeister',
slug: 'bob-der-baumeister', slug: 'bob-der-baumeister',
@ -108,8 +105,7 @@ export const searchResults = [
id: '7b654f72-f4da-4315-8bed-39de0859754b', id: '7b654f72-f4da-4315-8bed-39de0859754b',
__typename: 'User', __typename: 'User',
avatar: { avatar: {
url: url: 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/db/dbc9e03ebcc384b920c31542af2d27dd8eea9dc2_full.jpg',
}, },
name: 'Tonya Mohr', name: 'Tonya Mohr',
slug: 'tonya-mohr', slug: 'tonya-mohr',

View File

@ -4,7 +4,7 @@ export default (i18n) => {
const lang = i18n.locale().toUpperCase() const lang = i18n.locale().toUpperCase()
return { return {
CreateComment: gql` CreateComment: gql`
mutation($postId: ID!, $content: String!) { mutation ($postId: ID!, $content: String!) {
CreateComment(postId: $postId, content: $content) { CreateComment(postId: $postId, content: $content) {
id id
contentExcerpt contentExcerpt
@ -36,7 +36,7 @@ export default (i18n) => {
} }
`, `,
UpdateComment: gql` UpdateComment: gql`
mutation($content: String!, $id: ID!) { mutation ($content: String!, $id: ID!) {
UpdateComment(content: $content, id: $id) { UpdateComment(content: $content, id: $id) {
id id
contentExcerpt contentExcerpt

View File

@ -13,7 +13,7 @@ export const DonationsQuery = () => gql`
export const UpdateDonations = () => { export const UpdateDonations = () => {
return gql` return gql`
mutation($showDonations: Boolean, $goal: Int, $progress: Int) { mutation ($showDonations: Boolean, $goal: Int, $progress: Int) {
UpdateDonations(showDonations: $showDonations, goal: $goal, progress: $progress) { UpdateDonations(showDonations: $showDonations, goal: $goal, progress: $progress) {
id id
showDonations showDonations

View File

@ -1,7 +1,7 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const AddEmailAddressMutation = gql` export const AddEmailAddressMutation = gql`
mutation($email: String!) { mutation ($email: String!) {
AddEmailAddress(email: $email) { AddEmailAddress(email: $email) {
email email
createdAt createdAt
@ -10,7 +10,7 @@ export const AddEmailAddressMutation = gql`
` `
export const VerifyEmailAddressMutation = gql` export const VerifyEmailAddressMutation = gql`
mutation($email: String!, $nonce: String!) { mutation ($email: String!, $nonce: String!) {
VerifyEmailAddress(email: $email, nonce: $nonce) { VerifyEmailAddress(email: $email, nonce: $nonce) {
email email
verifiedAt verifiedAt

View File

@ -2,7 +2,7 @@ import gql from 'graphql-tag'
export default function () { export default function () {
return gql` return gql`
query($url: String!) { query ($url: String!) {
embed(url: $url) { embed(url: $url) {
type type
title title

View File

@ -3,7 +3,7 @@ import gql from 'graphql-tag'
export const reportsListQuery = () => { export const reportsListQuery = () => {
// no limit for the moment like before: "reports(first: 20, orderBy: createdAt_desc)" // no limit for the moment like before: "reports(first: 20, orderBy: createdAt_desc)"
return gql` return gql`
query( query (
$orderBy: ReportOrdering $orderBy: ReportOrdering
$first: Int $first: Int
$offset: Int $offset: Int
@ -94,7 +94,7 @@ export const reportsListQuery = () => {
export const reportMutation = () => { export const reportMutation = () => {
return gql` return gql`
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { mutation ($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport( fileReport(
resourceId: $resourceId resourceId: $resourceId
reasonCategory: $reasonCategory reasonCategory: $reasonCategory
@ -108,7 +108,7 @@ export const reportMutation = () => {
export const reviewMutation = () => { export const reviewMutation = () => {
return gql` return gql`
mutation($resourceId: ID!, $disable: Boolean, $closed: Boolean) { mutation ($resourceId: ID!, $disable: Boolean, $closed: Boolean) {
review(resourceId: $resourceId, disable: $disable, closed: $closed) { review(resourceId: $resourceId, disable: $disable, closed: $closed) {
disable disable
} }

View File

@ -3,7 +3,7 @@ import gql from 'graphql-tag'
export default () => { export default () => {
return { return {
CreatePost: gql` CreatePost: gql`
mutation($title: String!, $content: String!, $categoryIds: [ID], $image: ImageInput) { mutation ($title: String!, $content: String!, $categoryIds: [ID], $image: ImageInput) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds, image: $image) { CreatePost(title: $title, content: $content, categoryIds: $categoryIds, image: $image) {
title title
slug slug
@ -18,7 +18,7 @@ export default () => {
} }
`, `,
UpdatePost: gql` UpdatePost: gql`
mutation( mutation (
$id: ID! $id: ID!
$title: String! $title: String!
$content: String! $content: String!
@ -52,14 +52,14 @@ export default () => {
} }
`, `,
DeletePost: gql` DeletePost: gql`
mutation($id: ID!) { mutation ($id: ID!) {
DeletePost(id: $id) { DeletePost(id: $id) {
id id
} }
} }
`, `,
AddPostEmotionsMutation: gql` AddPostEmotionsMutation: gql`
mutation($to: _PostInput!, $data: _EMOTEDInput!) { mutation ($to: _PostInput!, $data: _EMOTEDInput!) {
AddPostEmotions(to: $to, data: $data) { AddPostEmotions(to: $to, data: $data) {
emotion emotion
from { from {
@ -72,7 +72,7 @@ export default () => {
} }
`, `,
RemovePostEmotionsMutation: gql` RemovePostEmotionsMutation: gql`
mutation($to: _PostInput!, $data: _EMOTEDInput!) { mutation ($to: _PostInput!, $data: _EMOTEDInput!) {
RemovePostEmotions(to: $to, data: $data) { RemovePostEmotions(to: $to, data: $data) {
emotion emotion
from { from {
@ -85,7 +85,7 @@ export default () => {
} }
`, `,
pinPost: gql` pinPost: gql`
mutation($id: ID!) { mutation ($id: ID!) {
pinPost(id: $id) { pinPost(id: $id) {
id id
title title
@ -102,7 +102,7 @@ export default () => {
} }
`, `,
unpinPost: gql` unpinPost: gql`
mutation($id: ID!) { mutation ($id: ID!) {
unpinPost(id: $id) { unpinPost(id: $id) {
id id
title title
@ -119,7 +119,7 @@ export default () => {
} }
`, `,
markTeaserAsViewed: gql` markTeaserAsViewed: gql`
mutation($id: ID!) { mutation ($id: ID!) {
markTeaserAsViewed(id: $id) { markTeaserAsViewed(id: $id) {
id id
} }

View File

@ -1,6 +1,6 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const SignupVerificationMutation = gql` export const SignupVerificationMutation = gql`
mutation( mutation (
$nonce: String! $nonce: String!
$name: String! $name: String!
$email: String! $email: String!

View File

@ -0,0 +1,9 @@
import gql from 'graphql-tag'
export default () => {
return gql`
mutation ($activeCategories: [String]) {
saveCategorySettings(activeCategories: $activeCategories)
}
`
}

View File

@ -5,7 +5,7 @@ export const searchQuery = gql`
${userFragment} ${userFragment}
${postFragment} ${postFragment}
query($query: String!) { query ($query: String!) {
searchResults(query: $query, limit: 5) { searchResults(query: $query, limit: 5) {
__typename __typename
... on Post { ... on Post {
@ -33,7 +33,7 @@ export const searchPosts = gql`
${postFragment} ${postFragment}
${tagsCategoriesAndPinnedFragment} ${tagsCategoriesAndPinnedFragment}
query($query: String!, $firstPosts: Int, $postsOffset: Int) { query ($query: String!, $firstPosts: Int, $postsOffset: Int) {
searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) { searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) {
postCount postCount
posts { posts {
@ -55,7 +55,7 @@ export const searchPosts = gql`
export const searchUsers = gql` export const searchUsers = gql`
${userFragment} ${userFragment}
query($query: String!, $firstUsers: Int, $usersOffset: Int) { query ($query: String!, $firstUsers: Int, $usersOffset: Int) {
searchUsers(query: $query, firstUsers: $firstUsers, usersOffset: $usersOffset) { searchUsers(query: $query, firstUsers: $firstUsers, usersOffset: $usersOffset) {
userCount userCount
users { users {
@ -67,7 +67,7 @@ export const searchUsers = gql`
` `
export const searchHashtags = gql` export const searchHashtags = gql`
query($query: String!, $firstHashtags: Int, $hashtagsOffset: Int) { query ($query: String!, $firstHashtags: Int, $hashtagsOffset: Int) {
searchHashtags(query: $query, firstHashtags: $firstHashtags, hashtagsOffset: $hashtagsOffset) { searchHashtags(query: $query, firstHashtags: $firstHashtags, hashtagsOffset: $hashtagsOffset) {
hashtagCount hashtagCount
hashtags { hashtags {

View File

@ -68,7 +68,7 @@ export const notificationQuery = (i18n) => {
${commentFragment} ${commentFragment}
${postFragment} ${postFragment}
query($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) { query ($read: Boolean, $orderBy: NotificationOrdering, $first: Int, $offset: Int) {
notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) { notifications(read: $read, orderBy: $orderBy, first: $first, offset: $offset) {
id id
read read
@ -107,7 +107,7 @@ export const markAsReadMutation = (i18n) => {
${commentFragment} ${commentFragment}
${postFragment} ${postFragment}
mutation($id: ID!) { mutation ($id: ID!) {
markAsRead(id: $id) { markAsRead(id: $id) {
id id
read read
@ -180,7 +180,7 @@ export const followUserMutation = (i18n) => {
${userFragment} ${userFragment}
${userCountsFragment} ${userCountsFragment}
mutation($id: ID!) { mutation ($id: ID!) {
followUser(id: $id) { followUser(id: $id) {
...user ...user
...userCounts ...userCounts
@ -200,7 +200,7 @@ export const unfollowUserMutation = (i18n) => {
${userFragment} ${userFragment}
${userCountsFragment} ${userCountsFragment}
mutation($id: ID!) { mutation ($id: ID!) {
unfollowUser(id: $id) { unfollowUser(id: $id) {
...user ...user
...userCounts ...userCounts
@ -217,7 +217,7 @@ export const unfollowUserMutation = (i18n) => {
export const updateUserMutation = () => { export const updateUserMutation = () => {
return gql` return gql`
mutation( mutation (
$id: ID! $id: ID!
$slug: String $slug: String
$name: String $name: String
@ -260,7 +260,7 @@ export const updateUserMutation = () => {
} }
export const checkSlugAvailableQuery = gql` export const checkSlugAvailableQuery = gql`
query($slug: String!) { query ($slug: String!) {
User(slug: $slug) { User(slug: $slug) {
slug slug
} }
@ -285,6 +285,7 @@ export const currentUserQuery = gql`
id id
url url
} }
activeCategories
} }
} }
` `
@ -303,7 +304,7 @@ export const userDataQuery = (i18n) => {
${userFragment} ${userFragment}
${postFragment} ${postFragment}
${commentFragment} ${commentFragment}
query($id: ID!) { query ($id: ID!) {
userData(id: $id) { userData(id: $id) {
user { user {
...user ...user

View File

@ -10,7 +10,7 @@ export const FetchAllRoles = () => {
export const updateUserRole = (role, id) => { export const updateUserRole = (role, id) => {
return gql` return gql`
mutation($role: UserRole!, $id: ID!) { mutation ($role: UserRole!, $id: ID!) {
switchUserRole(role: $role, id: $id) { switchUserRole(role: $role, id: $id) {
name name
role role

View File

@ -1,7 +1,7 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const queryLocations = () => gql` export const queryLocations = () => gql`
query($place: String!, $lang: String!) { query ($place: String!, $lang: String!) {
queryLocations(place: $place, lang: $lang) { queryLocations(place: $place, lang: $lang) {
place_name place_name
id id

View File

@ -20,7 +20,7 @@ export const blockedUsers = () => {
export const blockUser = () => { export const blockUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation ($id: ID!) {
blockUser(id: $id) { blockUser(id: $id) {
id id
name name
@ -33,7 +33,7 @@ export const blockUser = () => {
export const unblockUser = () => { export const unblockUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation ($id: ID!) {
unblockUser(id: $id) { unblockUser(id: $id) {
id id
name name

View File

@ -20,7 +20,7 @@ export const mutedUsers = () => {
export const muteUser = () => { export const muteUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation ($id: ID!) {
muteUser(id: $id) { muteUser(id: $id) {
id id
name name
@ -33,7 +33,7 @@ export const muteUser = () => {
export const unmuteUser = () => { export const unmuteUser = () => {
return gql` return gql`
mutation($id: ID!) { mutation ($id: ID!) {
unmuteUser(id: $id) { unmuteUser(id: $id) {
id id
name name

View File

@ -17,7 +17,16 @@
<base-button icon="bars" @click="toggleMobileMenuView" circle /> <base-button icon="bars" @click="toggleMobileMenuView" circle />
</ds-flex-item> </ds-flex-item>
<ds-flex-item <ds-flex-item
:width="{ v-if="categoriesActive && isLoggedIn"
:class="{ 'hide-mobile-menu': !toggleMobileMenu }"
style="flex-grow: 0; flex-basis: auto"
>
<client-only>
<categories-menu></categories-menu>
</client-only>
</ds-flex-item>
<ds-flex-item
:width="{
base: '45%', base: '45%',
sm: '45%', sm: '45%',
md: show ? 'auto' : '45%', md: show ? 'auto' : '45%',
@ -98,6 +107,7 @@ import FilterMenu from '~/components/FilterMenu/FilterMenu.vue'
import PageFooter from '~/components/PageFooter/PageFooter' import PageFooter from '~/components/PageFooter/PageFooter'
import AvatarMenu from '~/components/AvatarMenu/AvatarMenu' import AvatarMenu from '~/components/AvatarMenu/AvatarMenu'
import InviteButton from '~/components/InviteButton/InviteButton' import InviteButton from '~/components/InviteButton/InviteButton'
import CategoriesMenu from '~/components/FilterMenu/CategoriesMenu.vue'
export default { export default {
components: { components: {
@ -111,6 +121,7 @@ export default {
FilterMenu, FilterMenu,
PageFooter, PageFooter,
InviteButton, InviteButton,
CategoriesMenu,
}, },
mixins: [seo], mixins: [seo],
data() { data() {
@ -119,6 +130,7 @@ export default {
mobileSearchVisible: false, mobileSearchVisible: false,
toggleMobileMenu: false, toggleMobileMenu: false,
inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling, inviteRegistration: this.$env.INVITE_REGISTRATION === true, // for 'false' in .env INVITE_REGISTRATION is of type undefined and not(!) boolean false, because of internal handling,
categoriesActive: this.$env.CATEGORIES_ACTIVE,
} }
}, },
computed: { computed: {

Some files were not shown because too many files have changed in this diff Show More