diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 82b50aba3..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,157 +0,0 @@ -codecov: - #token: uuid # Your private repository token - #url: "http" # for Codecov Enterprise customers - #slug: "owner/repo" # for Codecov Enterprise customers - #branch: master # override the default branch - #bot: username # set user whom will be the consumer of oauth requests - #ci: # Custom CI domains if Codecov does not identify them automatically - # - ci.domain.com - # - !provider # ignore these providers when checking if CI passed - # # ex. You may test on Travis, Circle, and AppVeyor, but only need - # # to check if Travis passes. Therefore add: !circle and !appveyor - notify: - #after_n_builds: null # number of expected builds to recieve before sending notifications - # # after: check ci status unless disabled via require_ci_to_pass - require_ci_to_pass: yes # yes: will delay sending notifications until all ci is finished - # no: will send notifications without checking ci status and wait till "after_n_builds" are uploaded - #countdown: null # number of seconds to wait before first ci build check - #delay: null # number of seconds to wait between ci build checks - -coverage: - precision: 2 # 2 = xx.xx%, 0 = xx% - round: nearest # down|up|nearest - default down - # range: 50...60 # default 70...90. red...green - - #notify: - # irc: - # default: - # server: "chat.freenode.net"|encrypted - # branches: null # all branches by default - # threshold: 1% - # message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message - # flags: null - # paths: null - # - # slack: - # default: - # url: "http"|encrypted - # threshold: 1% - # branches: null # all branches by default - # message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message - # attachments: "sunburst, diff" - # only_pulls: false - # flags: null - # paths: null - # - # email: - # default: - # to: - # - example@domain.com - # - &author - # threshold: 1% - # only_pulls: false - # layout: header, diff, trends - # flags: null - # paths: null - # - # hipchat: - # default: - # url: "http"|encrypted - # room: name|id - # threshold: 1% - # token: encrypted - # branches: null # all branches by default - # notify: false # if the hipchat message is silent or loud (default false) - # message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message - # flags: null - # paths: null - # - # gitter: - # url: "http"|encrypted - # threshold: 1% - # branches: null # all branches by default - # message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message - # - # webhooks: - # _name_: - # url: "http"|encrypted - # threshold: 1% - # branches: null # all branches by default - - status: - project: - default: false # disable the default status that measures entire project - backend: # declare a new status context "backend" - against: parent - target: auto - threshold: null - #threshold: 1% - base: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: error - only_pulls: false - #branches: - # - master - #flags: - # - integration - paths: - - backend/ # only include coverage in "backend/" folder - webapp: # declare a new status context "frontend" - against: parent - target: auto - threshold: null - #threshold: 1% - base: auto - if_no_uploads: error - if_not_found: success - if_ci_failed: error - only_pulls: false - #branches: - # - master - #flags: - # - integration - paths: - - webapp/ # only include coverage in "webapp/" folder - - patch: - default: false - # against: parent - # target: 80% - # branches: null - # if_no_uploads: success - # if_not_found: success - # if_ci_failed: error - # only_pulls: false - # flags: - # - integration - # paths: - # - folder - - #changes: - # default: - # against: parent - # branches: null - # if_no_uploads: error - # if_not_found: success - # if_ci_failed: error - # only_pulls: false - # flags: - # - integration - # paths: - # - folder - - #flags: - # integration: - # branches: - # - master - # ignore: - # - app/ui - - #ignore: # files and folders for processing - # - tests/* - - #fixes: - # - "old_path::new_path" - -comment: off diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3cd77a905..000000000 --- a/.travis.yml +++ /dev/null @@ -1,84 +0,0 @@ -dist: xenial -language: node_js -node_js: lts/* -cache: - yarn: false - npm: false - -addons: - apt: - packages: - - libgconf-2-4 - snaps: - - docker - firefox: "latest-esr" - -install: - - yarn global add wait-on - # Install Codecov - - yarn install --frozen-lockfile - - cp backend/.env.template backend/.env - -before_script: - - docker-compose -f docker-compose.yml build --parallel - - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml build # just tagging, just be quite fast - - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml up -d - - wait-on http://localhost:7474 - - docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec backend yarn run db:migrate init - -script: - - export CYPRESS_RETRIES=1 - - export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) - - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH" - # Miscellaneous - - ./scripts/translations/sort.sh - - ./scripts/translations/missing-keys.sh - # Backend - - docker-compose exec backend yarn run lint - - docker-compose exec backend yarn run test --ci --verbose=false --coverage - - docker-compose exec backend yarn run db:seed - - docker-compose exec backend yarn run db:reset - # Frontend - - docker-compose exec webapp yarn run lint - - docker-compose exec webapp yarn run test --ci --verbose=false --coverage - # Fullstack - - docker-compose down - - docker-compose -f docker-compose.yml up -d - - wait-on http://localhost:7474 - # disable for last deploy, because of flakiness! - # - yarn run cypress:run --record - # - yarn run cucumber - # Coverage - # disable this uneffective thing for last deploy, because of easyness! - # - yarn run codecov - -after_success: - - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - - chmod +x send.sh - - ./send.sh success $WEBHOOK_URL - - if [ $TRAVIS_BRANCH == "master" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then - wget https://raw.githubusercontent.com/Human-Connection/Discord-Bot/develop/tester.sh && - chmod +x tester.sh && - ./tester.sh staging $WEBHOOK_URL; - fi - -after_failure: - - wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh - - chmod +x send.sh - - ./send.sh failure $WEBHOOK_URL - -before_deploy: - - go get -u github.com/tcnksm/ghr - # stop deployment to kubernetes until we have set it up - # - ./scripts/setup_kubernetes.sh - -deploy: - - provider: script - script: bash scripts/docker_push.sh - on: - branch: master - # stop deployment to kubernetes until we have set it up - # - provider: script - # script: bash scripts/deploy.sh - # on: - # branch: master diff --git a/backend/.env.template b/backend/.env.template index dd46846a9..c1742ef93 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -1,3 +1,5 @@ +DEBUG=true + NEO4J_URI=bolt://localhost:7687 NEO4J_USERNAME=neo4j NEO4J_PASSWORD=letmein diff --git a/backend/Dockerfile b/backend/Dockerfile index cd9bcdd3c..283e6c7fb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,7 +1,7 @@ ################################################################################## # BASE (Is pushed to DockerHub for rebranding) ################################### ################################################################################## -FROM node:19.9.0-alpine3.17 as base +FROM node:20.2.0-alpine3.17 as base # ENVs ## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame diff --git a/backend/package.json b/backend/package.json index 27c6fb4fc..7d27fb6f8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,7 +29,6 @@ "@babel/plugin-proposal-throw-expressions": "^7.8.3", "@babel/preset-env": "~7.9.5", "@babel/register": "^7.9.0", - "@hapi/joi": "^17.1.1", "@sentry/node": "^5.15.4", "apollo-cache-inmemory": "~1.6.5", "apollo-client": "~2.6.8", @@ -46,13 +45,10 @@ "cheerio": "~1.0.0-rc.3", "cors": "~2.8.5", "cross-env": "~7.0.3", - "date-fns": "2.22.1", "debug": "~4.1.1", "dotenv": "~8.2.0", "express": "^4.17.1", "graphql": "^14.6.0", - "graphql-custom-directives": "~0.2.14", - "graphql-iso-date": "~3.6.1", "graphql-middleware": "~4.0.2", "graphql-middleware-sentry": "^3.2.1", "graphql-redis-subscriptions": "^2.2.1", @@ -66,9 +62,8 @@ "lodash": "~4.17.14", "merge-graphql-schemas": "^1.7.8", "metascraper": "^5.33.5", - "metascraper-audio": "^5.33.5", + "metascraper-audio": "^5.34.4", "metascraper-author": "^5.33.5", - "metascraper-clearbit-logo": "^5.3.0", "metascraper-date": "^5.33.5", "metascraper-description": "^5.33.5", "metascraper-image": "^5.33.5", @@ -76,7 +71,7 @@ "metascraper-lang-detector": "^4.10.2", "metascraper-logo": "^5.33.5", "metascraper-publisher": "^5.33.5", - "metascraper-soundcloud": "^5.34.2", + "metascraper-soundcloud": "^5.34.4", "metascraper-title": "^5.33.5", "metascraper-url": "^5.34.2", "metascraper-video": "^5.33.5", @@ -91,19 +86,17 @@ "node-fetch": "~2.6.1", "nodemailer": "^6.4.4", "nodemailer-html-to-text": "^3.2.0", - "npm-run-all": "~4.1.5", "request": "~2.88.2", "sanitize-html": "~1.22.0", "slug": "~6.0.0", "subscriptions-transport-ws": "^0.9.19", "trunc-html": "~1.1.2", "uuid": "~8.3.2", - "validator": "^13.0.0", - "wait-on": "~4.0.1", + "validator": "^13.9.0", "xregexp": "^4.3.0" }, "devDependencies": { - "@faker-js/faker": "5.1.0", + "@faker-js/faker": "7.6.0", "apollo-server-testing": "~2.11.0", "chai": "~4.2.0", "cucumber": "~6.0.5", @@ -119,8 +112,7 @@ "jest": "29.4", "nodemon": "~2.0.2", "prettier": "~2.3.2", - "rosie": "^2.0.1", - "supertest": "~4.0.2" + "rosie": "^2.0.1" }, "resolutions": { "**/**/fs-capacitor": "^6.2.0", diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 44af5bbea..3f38b5b9c 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -2,7 +2,7 @@ import { v4 as uuid } from 'uuid' import slugify from 'slug' import { hashSync } from 'bcryptjs' import { Factory } from 'rosie' -import faker from '@faker-js/faker' +import { faker } from '@faker-js/faker' import { getDriver, getNeode } from './neo4j' import CONFIG from '../config/index.js' import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode.js' @@ -63,7 +63,7 @@ Factory.define('basicUser') .option('password', '1234') .attrs({ id: uuid, - name: faker.name.findName, + name: faker.name.fullName, password: '1234', role: 'user', termsAndConditionsAgreedVersion: '0.0.1', diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.js b/backend/src/db/migrations/20230329150329-article-label-for-posts.js new file mode 100644 index 000000000..3cf435203 --- /dev/null +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.js @@ -0,0 +1,53 @@ +import { getDriver } from '../../db/neo4j' + +export const description = 'Add to all existing posts the Article label' + +export async function up(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + await transaction.run(` + MATCH (post:Post) + SET post:Article + RETURN post + `) + await transaction.commit() + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} + +export async function down(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + await transaction.run(` + MATCH (post:Post) + REMOVE post:Article + RETURN post + `) + await transaction.commit() + next() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 8339e48a8..a8f8b9c68 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -2,7 +2,7 @@ import sample from 'lodash/sample' import { createTestClient } from 'apollo-server-testing' import CONFIG from '../config' import createServer from '../server' -import faker from '@faker-js/faker' +import { faker } from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' import { @@ -173,6 +173,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, { email: 'moderator@example.org', + avatar: null, }, ), Factory.build( @@ -209,6 +210,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, { email: 'dewey@example.org', + avatar: null, }, ), Factory.build( diff --git a/backend/src/graphql/GraphQL-Playground.md b/backend/src/graphql/GraphQL-Playground.md index af248f112..33e371eb5 100644 --- a/backend/src/graphql/GraphQL-Playground.md +++ b/backend/src/graphql/GraphQL-Playground.md @@ -1,5 +1,7 @@ # GraphQL Playground +***Attention:** For using the GraphQL Playground set `DEBUG=true` in your backend `.env`, see `.env.template`!* + To use GraphQL Playground, we need to know some basics: ## How To Login? diff --git a/backend/src/graphql/posts.js b/backend/src/graphql/posts.js index 2669d6f24..d1dc3ee45 100644 --- a/backend/src/graphql/posts.js +++ b/backend/src/graphql/posts.js @@ -11,6 +11,8 @@ export const createPostMutation = () => { $content: String! $categoryIds: [ID] $groupId: ID + $postType: PostType + $eventInput: _EventInput ) { CreatePost( id: $id @@ -19,11 +21,31 @@ export const createPostMutation = () => { content: $content categoryIds: $categoryIds groupId: $groupId + postType: $postType + eventInput: $eventInput ) { id slug title content + disabled + deleted + postType + author { + name + } + categories { + id + } + eventStart + eventEnd + eventLocationName + eventVenue + eventIsOnline + eventLocation { + lng + lat + } } } ` @@ -50,6 +72,7 @@ export const filterPosts = () => { id title content + eventStart } } ` diff --git a/backend/src/schema/resolvers/embeds/scraper.js b/backend/src/schema/resolvers/embeds/scraper.js index df37611f3..bcaa6e462 100644 --- a/backend/src/schema/resolvers/embeds/scraper.js +++ b/backend/src/schema/resolvers/embeds/scraper.js @@ -17,7 +17,6 @@ const metascraper = Metascraper([ require('metascraper-lang')(), require('metascraper-lang-detector')(), require('metascraper-logo')(), - // require('metascraper-clearbit-logo')(), require('metascraper-publisher')(), require('metascraper-title')(), require('metascraper-url')(), diff --git a/backend/src/schema/resolvers/filter-posts.spec.js b/backend/src/schema/resolvers/filter-posts.spec.js new file mode 100644 index 000000000..0b96e001f --- /dev/null +++ b/backend/src/schema/resolvers/filter-posts.spec.js @@ -0,0 +1,230 @@ +import { createTestClient } from 'apollo-server-testing' +import Factory, { cleanDatabase } from '../../db/factories' +import { getNeode, getDriver } from '../../db/neo4j' +import createServer from '../../server' +import CONFIG from '../../config' +import { filterPosts, createPostMutation } from '../../graphql/posts' + +CONFIG.CATEGORIES_ACTIVE = false + +const driver = getDriver() +const neode = getNeode() + +let query +let mutate +let authenticatedUser +let user + +beforeAll(async () => { + await cleanDatabase() + + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate +}) + +afterAll(async () => { + await cleanDatabase() + driver.close() +}) + +describe('Filter Posts', () => { + const now = new Date() + + beforeAll(async () => { + user = await Factory.build('user', { + id: 'user', + name: 'User', + about: 'I am a user.', + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createPostMutation(), + variables: { + id: 'a1', + title: 'I am an article', + content: 'I am an article written by user.', + }, + }) + await mutate({ + mutation: createPostMutation(), + variables: { + id: 'a2', + title: 'I am anonther article', + content: 'I am another article written by user.', + }, + }) + await mutate({ + mutation: createPostMutation(), + variables: { + id: 'e1', + title: 'Illegaler Kindergeburtstag', + content: 'Elli wird fünf. Wir feiern ihren Geburtstag.', + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventVenue: 'Garten der Familie Maier', + }, + }, + }) + await mutate({ + mutation: createPostMutation(), + variables: { + id: 'e2', + title: 'Räuber-Treffen', + content: 'Planung der nächsten Räuberereien', + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(), + eventVenue: 'Wirtshaus im Spessart', + }, + }, + }) + }) + + describe('no filters set', () => { + it('finds all posts', async () => { + const { + data: { Post: result }, + } = await query({ query: filterPosts() }) + expect(result).toHaveLength(4) + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'a1' }), + expect.objectContaining({ id: 'a2' }), + expect.objectContaining({ id: 'e1' }), + expect.objectContaining({ id: 'e2' }), + ]), + ) + }) + }) + + describe('post type filter set to ["Article"]', () => { + it('finds the articles', async () => { + const { + data: { Post: result }, + } = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Article'] } } }) + expect(result).toHaveLength(2) + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'a1' }), + expect.objectContaining({ id: 'a2' }), + ]), + ) + }) + }) + + describe('post type filter set to ["Event"]', () => { + it('finds the articles', async () => { + const { + data: { Post: result }, + } = await query({ query: filterPosts(), variables: { filter: { postType_in: ['Event'] } } }) + expect(result).toHaveLength(2) + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'e1' }), + expect.objectContaining({ id: 'e2' }), + ]), + ) + }) + }) + + describe('post type filter set to ["Article", "Event"]', () => { + it('finds all posts', async () => { + const { + data: { Post: result }, + } = await query({ + query: filterPosts(), + variables: { filter: { postType_in: ['Article', 'Event'] } }, + }) + expect(result).toHaveLength(4) + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'a1' }), + expect.objectContaining({ id: 'a2' }), + expect.objectContaining({ id: 'e1' }), + expect.objectContaining({ id: 'e2' }), + ]), + ) + }) + }) + + describe('order events by event start descending', () => { + it('finds the events orderd accordingly', async () => { + const { + data: { Post: result }, + } = await query({ + query: filterPosts(), + variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_desc'] }, + }) + expect(result).toHaveLength(2) + expect(result).toEqual([ + expect.objectContaining({ + id: 'e1', + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }), + expect.objectContaining({ + id: 'e2', + eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(), + }), + ]) + }) + }) + + describe('order events by event start ascending', () => { + it('finds the events orderd accordingly', async () => { + const { + data: { Post: result }, + } = await query({ + query: filterPosts(), + variables: { filter: { postType_in: ['Event'] }, orderBy: ['eventStart_asc'] }, + }) + expect(result).toHaveLength(2) + expect(result).toEqual([ + expect.objectContaining({ + id: 'e2', + eventStart: new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString(), + }), + expect.objectContaining({ + id: 'e1', + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }), + ]) + }) + }) + + describe('filter events by event start date', () => { + it('finds only events after given date', async () => { + const { + data: { Post: result }, + } = await query({ + query: filterPosts(), + variables: { + filter: { + postType_in: ['Event'], + eventStart_gte: new Date( + now.getFullYear(), + now.getMonth(), + now.getDate() + 2, + ).toISOString(), + }, + }, + }) + expect(result).toHaveLength(1) + expect(result).toEqual([ + expect.objectContaining({ + id: 'e1', + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }), + ]) + }) + }) +}) diff --git a/backend/src/schema/resolvers/helpers/events.js b/backend/src/schema/resolvers/helpers/events.js new file mode 100644 index 000000000..84e64299d --- /dev/null +++ b/backend/src/schema/resolvers/helpers/events.js @@ -0,0 +1,47 @@ +import { UserInputError } from 'apollo-server' + +export const validateEventParams = (params) => { + if (params.postType && params.postType === 'Event') { + const { eventInput } = params + validateEventDate(eventInput.eventStart) + params.eventStart = eventInput.eventStart + if (eventInput.eventEnd) { + validateEventEnd(eventInput.eventStart, eventInput.eventEnd) + params.eventEnd = eventInput.eventEnd + } + if (eventInput.eventLocationName && !eventInput.eventVenue) { + throw new UserInputError('Event venue must be present if event location is given!') + } + params.eventVenue = eventInput.eventVenue + params.eventLocationName = eventInput.eventLocationName + params.eventIsOnline = !!eventInput.eventIsOnline + } + delete params.eventInput + let locationName + if (params.eventLocationName) { + locationName = params.eventLocationName + } else { + params.eventLocationName = null + locationName = null + } + return locationName +} + +const validateEventDate = (dateString) => { + const date = new Date(dateString) + if (date.toString() === 'Invalid Date') + throw new UserInputError('Event start date must be a valid date!') + const now = new Date() + if (date.getTime() < now.getTime()) { + throw new UserInputError('Event start date must be in the future!') + } +} + +const validateEventEnd = (start, end) => { + const endDate = new Date(end) + if (endDate.toString() === 'Invalid Date') + throw new UserInputError('Event end date must be a valid date!') + const startDate = new Date(start) + if (endDate < startDate) + throw new UserInputError('Event end date must be a after event start date!') +} diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 117b9b530..c3b882146 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -51,7 +51,7 @@ export default { OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(relatedUser) WITH user, notification, resource, membership, relatedUser, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author)} ] AS posts + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts WITH resource, user, notification, authors, posts, relatedUser, membership, resource {.*, __typename: labels(resource)[0], @@ -90,7 +90,7 @@ export default { SET notification.read = TRUE WITH user, notification, resource, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user) WITH resource, user, notification, authors, posts, membership, resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role } AS finalResource @@ -120,7 +120,7 @@ export default { SET notification.read = TRUE WITH user, notification, resource, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author), postType: filter(l IN labels(post) WHERE NOT l = "Post")} ] AS posts OPTIONAL MATCH (resource)<-[membership:MEMBER_OF]-(user) WITH resource, user, notification, authors, posts, membership, resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], myRole: membership.role} AS finalResource diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 063b2210d..c7d2eebdb 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -7,6 +7,8 @@ import Resolver from './helpers/Resolver' import { filterForMutedUsers } from './helpers/filterForMutedUsers' import { filterInvisiblePosts } from './helpers/filterInvisiblePosts' import { filterPostsOfMyGroups } from './helpers/filterPostsOfMyGroups' +import { validateEventParams } from './helpers/events' +import { createOrUpdateLocations } from './users/location' import CONFIG from '../../config' const maintainPinnedPosts = (params) => { @@ -81,6 +83,9 @@ export default { CreatePost: async (_parent, params, context, _resolveInfo) => { const { categoryIds, groupId } = params const { image: imageInput } = params + + const locationName = validateEventParams(params) + delete params.categoryIds delete params.image delete params.groupId @@ -125,12 +130,13 @@ export default { SET post.updatedAt = toString(datetime()) SET post.clickedCount = 0 SET post.viewedTeaserCount = 0 + SET post:${params.postType} WITH post MATCH (author:User {id: $userId}) MERGE (post)<-[:WROTE]-(author) ${categoriesCypher} ${groupCypher} - RETURN post {.*} + RETURN post {.*, postType: filter(l IN labels(post) WHERE NOT l = "Post") } `, { userId: context.user.id, categoryIds, groupId, params }, ) @@ -142,6 +148,9 @@ export default { }) try { const post = await writeTxResultPromise + if (locationName) { + await createOrUpdateLocations('Post', post.id, locationName, session) + } return post } catch (e) { if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') @@ -154,6 +163,9 @@ export default { UpdatePost: async (_parent, params, context, _resolveInfo) => { const { categoryIds } = params const { image: imageInput } = params + + const locationName = validateEventParams(params) + delete params.categoryIds delete params.image const session = context.driver.session() @@ -183,7 +195,16 @@ export default { ` } - updatePostCypher += `RETURN post {.*}` + if (params.postType) { + updatePostCypher += ` + REMOVE post:Article + REMOVE post:Event + SET post:${params.postType} + WITH post + ` + } + + updatePostCypher += `RETURN post {.*, postType: filter(l IN labels(post) WHERE NOT l = "Post")}` const updatePostVariables = { categoryIds, params } try { const writeTxResultPromise = session.writeTransaction(async (transaction) => { @@ -196,6 +217,9 @@ export default { return post }) const post = await writeTxResultPromise + if (locationName) { + await createOrUpdateLocations('Post', post.id, locationName, session) + } return post } finally { session.close() @@ -385,7 +409,19 @@ export default { }, Post: { ...Resolver('Post', { - undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'], + undefinedToNull: [ + 'activityId', + 'objectId', + 'language', + 'pinnedAt', + 'pinned', + 'eventVenue', + 'eventLocation', + 'eventLocationName', + 'eventStart', + 'eventEnd', + 'eventIsOnline', + ], hasMany: { tags: '-[:TAGGED]->(related:Tag)', categories: '-[:CATEGORIZED]->(related:Category)', @@ -398,6 +434,7 @@ export default { pinnedBy: '<-[:PINNED]-(related:User)', image: '-[:HERO_IMAGE]->(related:Image)', group: '-[:IN]->(related:Group)', + eventLocation: '-[:IS_IN]->(related:Location)', }, count: { commentsCount: diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 9335c1313..87d09e262 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -3,6 +3,10 @@ import Factory, { cleanDatabase } from '../../db/factories' import gql from 'graphql-tag' import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' +import { createPostMutation } from '../../graphql/posts' +import CONFIG from '../../config' + +CONFIG.CATEGORIES_ACTIVE = true const driver = getDriver() const neode = getNeode() @@ -15,29 +19,6 @@ let user const categoryIds = ['cat9', 'cat4', 'cat15'] let variables -const createPostMutation = gql` - mutation ($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) { - CreatePost( - id: $id - title: $title - content: $content - language: $language - categoryIds: $categoryIds - ) { - id - title - content - slug - disabled - deleted - language - author { - name - } - } - } -` - beforeAll(async () => { await cleanDatabase() @@ -281,7 +262,7 @@ describe('CreatePost', () => { describe('unauthenticated', () => { 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 Authorized!') }) }) @@ -296,7 +277,7 @@ describe('CreatePost', () => { data: { CreatePost: { title: 'I am a title', content: 'Some content' } }, errors: undefined, } - await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( + await expect(mutate({ mutation: createPostMutation(), variables })).resolves.toMatchObject( expected, ) }) @@ -313,25 +294,327 @@ describe('CreatePost', () => { }, errors: undefined, } - await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( + await expect(mutate({ mutation: createPostMutation(), variables })).resolves.toMatchObject( expected, ) }) it('`disabled` and `deleted` default to `false`', async () => { const expected = { data: { CreatePost: { disabled: false, deleted: false } } } - await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( + await expect(mutate({ mutation: createPostMutation(), variables })).resolves.toMatchObject( expected, ) }) + + it('has label "Article" as default', async () => { + await expect(mutate({ mutation: createPostMutation(), variables })).resolves.toMatchObject({ + data: { CreatePost: { postType: ['Article'] } }, + }) + }) + + describe('with invalid post type', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: createPostMutation(), + variables: { ...variables, postType: 'not-valid' }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: + 'Variable "$postType" got invalid value "not-valid"; Expected type PostType.', + }, + ], + }) + }) + }) + + describe('with post type "Event"', () => { + describe('without event start date', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: "Cannot read properties of undefined (reading 'eventStart')", + }, + ], + }) + }) + }) + + describe('with invalid event start date', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: 'no date', + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event start date must be a valid date!', + }, + ], + }) + }) + }) + + describe('with event start date in the past', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() - 1).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event start date must be in the future!', + }, + ], + }) + }) + }) + + describe('with valid start date and invalid end date', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventEnd: 'not-valid', + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event end date must be a valid date!', + }, + ], + }) + }) + }) + + describe('with valid start date and end date before start date', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 2).toISOString(), + eventEnd: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event end date must be a after event start date!', + }, + ], + }) + }) + }) + + describe('with valid start date and valid end date', () => { + it('creates the event', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventEnd: new Date(now.getFullYear(), now.getMonth() + 2).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventEnd: new Date(now.getFullYear(), now.getMonth() + 2).toISOString(), + eventIsOnline: false, + }, + }, + errors: undefined, + }) + }) + }) + + describe('with valid start date and event is online', () => { + it('creates the event', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventIsOnline: true, + }, + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventIsOnline: true, + }, + }, + errors: undefined, + }) + }) + }) + + describe('event location name is given but event venue is missing', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Berlin', + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event venue must be present if event location is given!', + }, + ], + }) + }) + }) + + describe('valid event input without location', () => { + it('has label "Event" set', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventIsOnline: false, + }, + }, + errors: undefined, + }) + }) + }) + + describe('valid event input with location name', () => { + it('has label "Event" set', async () => { + const now = new Date() + await expect( + mutate({ + mutation: createPostMutation(), + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Leipzig', + eventVenue: 'Connewitzer Kreuz', + }, + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Leipzig', + eventVenue: 'Connewitzer Kreuz', + eventLocation: { + lng: 12.374733, + lat: 51.340632, + }, + }, + }, + errors: undefined, + }) + }) + }) + }) }) }) describe('UpdatePost', () => { let author, newlyCreatedPost const updatePostMutation = gql` - mutation ($id: ID!, $title: String!, $content: String!, $image: ImageInput) { - UpdatePost(id: $id, title: $title, content: $content, image: $image) { + mutation ( + $id: ID! + $title: String! + $content: String! + $image: ImageInput + $categoryIds: [ID] + $postType: PostType + $eventInput: _EventInput + ) { + UpdatePost( + id: $id + title: $title + content: $content + image: $image + categoryIds: $categoryIds + postType: $postType + eventInput: $eventInput + ) { id title content @@ -341,26 +624,34 @@ describe('UpdatePost', () => { } createdAt updatedAt + categories { + id + } + postType + eventStart + eventLocationName + eventVenue + eventLocation { + lng + lat + } } } ` beforeEach(async () => { author = await Factory.build('user', { slug: 'the-author' }) - newlyCreatedPost = await Factory.build( - 'post', - { - id: 'p9876', + authenticatedUser = await author.toJson() + const { data } = await mutate({ + mutation: createPostMutation(), + variables: { title: 'Old title', content: 'Old content', - }, - { - author, categoryIds, }, - ) - + }) + newlyCreatedPost = data.CreatePost variables = { - id: 'p9876', + id: newlyCreatedPost.id, title: 'New title', content: 'New content', } @@ -394,7 +685,7 @@ describe('UpdatePost', () => { it('updates a post', async () => { const expected = { - data: { UpdatePost: { id: 'p9876', content: 'New content' } }, + data: { UpdatePost: { id: newlyCreatedPost.id, content: 'New content' } }, errors: undefined, } await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( @@ -405,7 +696,11 @@ describe('UpdatePost', () => { it('updates a post, but maintains non-updated attributes', async () => { const expected = { data: { - UpdatePost: { id: 'p9876', content: 'New content', createdAt: expect.any(String) }, + UpdatePost: { + id: newlyCreatedPost.id, + content: 'New content', + createdAt: expect.any(String), + }, }, errors: undefined, } @@ -415,23 +710,20 @@ describe('UpdatePost', () => { }) it('updates the updatedAt attribute', async () => { - newlyCreatedPost = await newlyCreatedPost.toJson() const { data: { UpdatePost }, } = await mutate({ mutation: updatePostMutation, variables }) - expect(newlyCreatedPost.updatedAt).toBeTruthy() - expect(Date.parse(newlyCreatedPost.updatedAt)).toEqual(expect.any(Number)) expect(UpdatePost.updatedAt).toBeTruthy() expect(Date.parse(UpdatePost.updatedAt)).toEqual(expect.any(Number)) expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt) }) - /* describe('no new category ids provided for update', () => { + describe('no new category ids provided for update', () => { it('resolves and keeps current categories', async () => { const expected = { data: { UpdatePost: { - id: 'p9876', + id: newlyCreatedPost.id, categories: expect.arrayContaining([{ id: 'cat9' }, { id: 'cat4' }, { id: 'cat15' }]), }, }, @@ -441,9 +733,9 @@ describe('UpdatePost', () => { expected, ) }) - }) */ + }) - /* describe('given category ids', () => { + describe('given category ids', () => { beforeEach(() => { variables = { ...variables, categoryIds: ['cat27'] } }) @@ -452,7 +744,7 @@ describe('UpdatePost', () => { const expected = { data: { UpdatePost: { - id: 'p9876', + id: newlyCreatedPost.id, categories: expect.arrayContaining([{ id: 'cat27' }]), }, }, @@ -462,9 +754,160 @@ describe('UpdatePost', () => { expected, ) }) - }) */ + }) - describe('params.image', () => { + describe('change post type to event', () => { + describe('with missing event start date', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: updatePostMutation, + variables: { ...variables, postType: 'Event' }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: "Cannot read properties of undefined (reading 'eventStart')", + }, + ], + }) + }) + }) + + describe('with invalid event start date', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: updatePostMutation, + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: 'no-date', + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event start date must be a valid date!', + }, + ], + }) + }) + }) + + describe('with event start date in the past', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: updatePostMutation, + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() - 1).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event start date must be in the future!', + }, + ], + }) + }) + }) + + describe('event location name is given but event venue is missing', () => { + it('throws an error', async () => { + const now = new Date() + await expect( + mutate({ + mutation: updatePostMutation, + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Berlin', + }, + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Event venue must be present if event location is given!', + }, + ], + }) + }) + }) + + describe('valid event input without location name', () => { + it('has label "Event" set', async () => { + const now = new Date() + await expect( + mutate({ + mutation: updatePostMutation, + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }, + }, + }), + ).resolves.toMatchObject({ + data: { + UpdatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + }, + }, + errors: undefined, + }) + }) + }) + + describe('valid event input with location name', () => { + it('has label "Event" set', async () => { + const now = new Date() + await expect( + mutate({ + mutation: updatePostMutation, + variables: { + ...variables, + postType: 'Event', + eventInput: { + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Leipzig', + eventVenue: 'Connewitzer Kreuz', + }, + }, + }), + ).resolves.toMatchObject({ + data: { + UpdatePost: { + postType: ['Event'], + eventStart: new Date(now.getFullYear(), now.getMonth() + 1).toISOString(), + eventLocationName: 'Leipzig', + eventVenue: 'Connewitzer Kreuz', + eventLocation: { + lng: 12.374733, + lat: 51.340632, + }, + }, + }, + errors: undefined, + }) + }) + }) + }) + + describe.skip('params.image', () => { describe('is object', () => { beforeEach(() => { variables = { ...variables, image: { sensitive: true } } diff --git a/backend/src/schema/resolvers/postsInGroups.spec.js b/backend/src/schema/resolvers/postsInGroups.spec.js index 86a278207..ba9041090 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.js +++ b/backend/src/schema/resolvers/postsInGroups.spec.js @@ -818,11 +818,13 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, ]), }, @@ -846,11 +848,13 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, ]), }, @@ -874,11 +878,13 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, ]), }, @@ -902,11 +908,13 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, ]), }, @@ -930,21 +938,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1319,16 +1331,19 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, ]), }, @@ -1361,21 +1376,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1410,16 +1429,19 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1452,11 +1474,13 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, ]), }, @@ -1489,21 +1513,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1534,21 +1562,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1579,21 +1611,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1628,21 +1664,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1675,21 +1715,25 @@ describe('Posts in Groups', () => { id: 'post-to-public-group', title: 'A post to a public group', content: 'I am posting into a public group as a member of the group', + eventStart: null, }, { id: 'post-without-group', title: 'A post without a group', content: 'I am a user who does not belong to a group yet.', + eventStart: null, }, { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, @@ -1739,11 +1783,13 @@ describe('Posts in Groups', () => { id: 'post-to-closed-group', title: 'A post to a closed group', content: 'I am posting into a closed group as a member of the group', + eventStart: null, }, { id: 'post-to-hidden-group', title: 'A post to a hidden group', content: 'I am posting into a hidden group as a member of the group', + eventStart: null, }, ]), }, diff --git a/backend/src/schema/resolvers/users/location.js b/backend/src/schema/resolvers/users/location.js index 9d8a11f89..54d73560b 100644 --- a/backend/src/schema/resolvers/users/location.js +++ b/backend/src/schema/resolvers/users/location.js @@ -22,7 +22,7 @@ const locales = ['en', 'de', 'fr', 'nl', 'it', 'es', 'pt', 'pl', 'ru'] const createLocation = async (session, mapboxData) => { const data = { - id: mapboxData.id, + id: mapboxData.id + (mapboxData.address ? `-${mapboxData.address}` : ''), nameEN: mapboxData.text_en, nameDE: mapboxData.text_de, nameFR: mapboxData.text_fr, @@ -33,6 +33,7 @@ const createLocation = async (session, mapboxData) => { namePL: mapboxData.text_pl, nameRU: mapboxData.text_ru, type: mapboxData.id.split('.')[0].toLowerCase(), + address: mapboxData.address, lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null, lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null, } @@ -54,6 +55,10 @@ const createLocation = async (session, mapboxData) => { if (data.lat && data.lng) { mutation += ', l.lat = $lat, l.lng = $lng' } + if (data.address) { + mutation += ', l.address = $address' + } + mutation += ' RETURN l.id' await session.writeTransaction((transaction) => { @@ -72,7 +77,7 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s locationName, )}.json?access_token=${ CONFIG.MAPBOX_TOKEN - }&types=region,place,country&language=${locales.join(',')}`, + }&types=region,place,country,address&language=${locales.join(',')}`, ) debug(res) @@ -103,6 +108,10 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s let parent = data + if (parent.address) { + parent.id += `-${parent.address}` + } + if (data.context) { await asyncForEach(data.context, async (ctx) => { await createLocation(session, ctx) diff --git a/backend/src/schema/types/enum/PostType.gql b/backend/src/schema/types/enum/PostType.gql new file mode 100644 index 000000000..eef80d6ba --- /dev/null +++ b/backend/src/schema/types/enum/PostType.gql @@ -0,0 +1,4 @@ +enum PostType { + Article + Event +} diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 6fc7a3215..5a5d57b9a 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -83,6 +83,8 @@ input _PostFilter { emotions_every: _PostEMOTEDFilter group: _GroupFilter postsInMyGroups: Boolean + postType_in: [PostType] + eventStart_gte: String } enum _PostOrdering { @@ -104,6 +106,8 @@ enum _PostOrdering { language_desc pinned_asc pinned_desc + eventStart_asc + eventStart_desc } @@ -171,12 +175,30 @@ type Post { @cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)") group: Group @relation(name: "IN", direction: "OUT") + + postType: [PostType] + @cypher(statement: "RETURN filter(l IN labels(this) WHERE NOT l = 'Post')") + + eventLocationName: String + eventLocation: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") + eventVenue: String + eventStart: String + eventEnd: String + eventIsOnline: Boolean } input _PostInput { id: ID! } +input _EventInput { + eventStart: String! + eventEnd: String + eventVenue: String + eventLocationName: String + eventIsOnline: Boolean +} + type Mutation { CreatePost( id: ID @@ -189,6 +211,8 @@ type Mutation { categoryIds: [ID] contentExcerpt: String groupId: ID + postType: PostType = Article + eventInput: _EventInput ): Post UpdatePost( id: ID! @@ -200,6 +224,8 @@ type Mutation { visibility: Visibility language: String categoryIds: [ID] + postType: PostType + eventInput: _EventInput ): Post DeletePost(id: ID!): Post AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED diff --git a/backend/yarn.lock b/backend/yarn.lock index 18887b92a..d576416fc 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1088,10 +1088,10 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@faker-js/faker@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f" - integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q== +"@faker-js/faker@7.6.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.6.0.tgz#9ea331766084288634a9247fcd8b84f16ff4ba07" + integrity sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw== "@gar/promisify@^1.1.3": version "1.1.3" @@ -1131,33 +1131,16 @@ resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== -"@hapi/address@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.0.1.tgz#267301ddf7bc453718377a6fb3832a2f04a721dd" - integrity sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/bourne@1.x.x": version "1.3.2" resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== -"@hapi/formula@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" - integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== - "@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": version "8.5.1" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== -"@hapi/hoek@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.0.tgz#ba83436edfac1d1ffd0e94797d43419c20ad49b8" - integrity sha512-XxD4A5YMIH70ddjG7BJBUz7RWVQAwIP/36Eoyh0DsaWp92OAeXkrbtSEaYkynBPTsN9Uv2mZq9QWZYILl2Svrw== - "@hapi/joi@^15.1.1": version "15.1.1" resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" @@ -1168,22 +1151,6 @@ "@hapi/hoek" "8.x.x" "@hapi/topo" "3.x.x" -"@hapi/joi@^17.1.0", "@hapi/joi@^17.1.1": - version "17.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350" - integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg== - dependencies: - "@hapi/address" "^4.0.1" - "@hapi/formula" "^2.0.0" - "@hapi/hoek" "^9.0.0" - "@hapi/pinpoint" "^2.0.0" - "@hapi/topo" "^5.0.0" - -"@hapi/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" - integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== - "@hapi/topo@3.x.x": version "3.1.6" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" @@ -1191,13 +1158,6 @@ dependencies: "@hapi/hoek" "^8.3.0" -"@hapi/topo@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" - integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== - dependencies: - "@hapi/hoek" "^9.0.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1485,6 +1445,13 @@ dependencies: json-buffer "~3.0.1" +"@keyvhq/core@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@keyvhq/core/-/core-2.0.0.tgz#518311a59dbc4d6b6fd56852c15f483edb9fc7c9" + integrity sha512-fi3+F7GNImn1j4r6UFhsHRwN8a05uhUlrbNWZgnkX0h1NzcBEPNNqqMOE4KSASJwH2e9Eh/jm+bEfto58csNgg== + dependencies: + json-buffer "~3.0.1" + "@keyvhq/memoize@~1.6.14": version "1.6.14" resolved "https://registry.yarnpkg.com/@keyvhq/memoize/-/memoize-1.6.14.tgz#6a347b81e9be7040c678f8fc4b9046186484b6fc" @@ -1493,6 +1460,14 @@ "@keyvhq/core" "^1.6.14" mimic-fn "~3.0.0" +"@keyvhq/memoize@~2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@keyvhq/memoize/-/memoize-2.0.0.tgz#58b1118c2972b631aede81040eab6daefce8f5e8" + integrity sha512-0vibPmDh7nCWsbdS4A1IJX1G89PyT+nJEDGj1tUYsLPQ5BLnpLwneCudmXIvxD6cUUvMYqmhxKyjpvHd8DpPkA== + dependencies: + "@keyvhq/core" "^2.0.0" + mimic-fn "~3.0.0" + "@metascraper/helpers@^4.10.2": version "4.10.2" resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-4.10.2.tgz#864e49c71468548441f3a1ab68fdb2913855821b" @@ -1516,15 +1491,15 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@metascraper/helpers@^5.33.5", "@metascraper/helpers@^5.34.2": - version "5.34.2" - resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.34.2.tgz#3c6ef10c1ab7e8b09b67219c91c7b8d30fedd0c1" - integrity sha512-7mlQ1uYCUGw5OZU52leHbbjO8UrmmTvLCt6p4E4yWAvEW1oi9t7Pq1AmizX4hNUW+FgMvXCcv4t5BCfxiY8LhA== +"@metascraper/helpers@^5.33.5", "@metascraper/helpers@^5.34.2", "@metascraper/helpers@^5.34.4": + version "5.34.4" + resolved "https://registry.yarnpkg.com/@metascraper/helpers/-/helpers-5.34.4.tgz#59faf01466938b26aa8df147f97c7f9f4d739d28" + integrity sha512-OZdXkfxJXH5dW+aoptLJzxN56Xj+ABzbqZ9NDuKn908zW4tvLBPD6go3qdd3GXXQZH7TxvWpETn9i1AxzoyKmQ== dependencies: audio-extensions "0.0.0" chrono-node "~2.6.2" condense-whitespace "~2.0.0" - entities "~4.4.0" + entities "~4.5.0" file-extension "~4.0.5" has-values "~2.0.1" image-extensions "~1.1.0" @@ -1532,7 +1507,7 @@ is-uri "~1.2.4" iso-639-3 "~2.2.0" isostring "0.0.1" - jsdom "~21.1.1" + jsdom "~22.0.0" lodash "~4.17.21" memoize-one "~6.0.0" microsoft-capitalize "~1.0.5" @@ -1718,11 +1693,6 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g== -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - "@sindresorhus/is@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" @@ -1742,13 +1712,6 @@ dependencies: "@sinonjs/commons" "^2.0.0" -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -2143,34 +2106,16 @@ accepts@^1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-globals@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" - integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== - dependencies: - acorn "^8.1.0" - acorn-walk "^8.0.2" - acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== -acorn-walk@^8.0.2: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - acorn@^7.1.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.1.0, acorn@^8.8.2: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" @@ -3184,19 +3129,6 @@ cacheable-lookup@^5.0.3: resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - cacheable-request@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" @@ -3613,7 +3545,7 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.0, component-emitter@^1.2.1: +component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -3699,11 +3631,6 @@ cookie@^0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= -cookiejar@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" - integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== - copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" @@ -3920,11 +3847,6 @@ data-urls@^4.0.0: whatwg-mimetype "^3.0.0" whatwg-url "^12.0.0" -date-fns@2.22.1: - version "2.22.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4" - integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg== - dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -3949,7 +3871,7 @@ debug@4, debug@^4.0.1, debug@~4.1.1: dependencies: ms "^2.1.1" -debug@^3.1.0, debug@^3.2.6: +debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3973,13 +3895,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -4019,11 +3934,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== -defer-to-connect@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.0.2.tgz#4bae758a314b034ae33902b5aac25a8dd6a8633e" - integrity sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw== - defer-to-connect@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" @@ -4386,11 +4296,16 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== -entities@^4.2.0, entities@^4.3.0, entities@^4.4.0, entities@~4.4.0: +entities@^4.2.0, entities@^4.3.0, entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== +entities@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -4415,7 +4330,7 @@ error-stack-parser@^2.0.1: dependencies: stackframe "^1.0.4" -es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.12.0, es-abstract@^1.5.1, es-abstract@^1.7.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -4529,18 +4444,6 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - eslint-config-prettier@~6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9" @@ -4710,7 +4613,7 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -4734,11 +4637,6 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -4898,7 +4796,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -5105,15 +5003,6 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== -form-data@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37" - integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -5141,11 +5030,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formidable@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" - integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -5217,7 +5101,7 @@ fsevents@~2.1.1: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== -function-bind@^1.0.2, function-bind@^1.1.1: +function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== @@ -5285,7 +5169,7 @@ get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= -get-stream@^4.0.0, get-stream@^4.1.0: +get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -5424,7 +5308,7 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -got@~11.8.0, got@~11.8.5: +got@~11.8.0, got@~11.8.5, got@~11.8.6: version "11.8.6" resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== @@ -5441,23 +5325,6 @@ got@~11.8.0, got@~11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" -got@~9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -5482,16 +5349,6 @@ graphql-auth-directives@^2.1.0: graphql-tools "^4.0.4" jsonwebtoken "^8.3.0" -graphql-custom-directives@~0.2.14: - version "0.2.14" - resolved "https://registry.yarnpkg.com/graphql-custom-directives/-/graphql-custom-directives-0.2.14.tgz#88611b8cb074477020ad85af47bfe168c4c23992" - integrity sha512-c3+r+st7dbBNGOLumkWrnv4nwAHJr1sZnkYc72AIMtzjuQ4+Slk1ZsFVYt1kwXJpfxXgf6g2g0jYc9+Lmz4ENg== - dependencies: - libphonenumber-js "^1.6.4" - lodash "^4.17.5" - moment "^2.22.2" - numeral "^2.0.6" - graphql-extensions@^0.12.6: version "0.12.6" resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.12.6.tgz#c66be43035662a8cfb0b8efe9df96595338bd13c" @@ -5501,11 +5358,6 @@ graphql-extensions@^0.12.6: apollo-server-env "^2.4.5" apollo-server-types "^0.6.1" -graphql-iso-date@~3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96" - integrity sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q== - graphql-middleware-sentry@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/graphql-middleware-sentry/-/graphql-middleware-sentry-3.2.1.tgz#b5653d78903d655cf4212a602cfa6e26689cda07" @@ -6895,25 +6747,22 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== -jsdom@~21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.1.1.tgz#ab796361e3f6c01bcfaeda1fea3c06197ac9d8ae" - integrity sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w== +jsdom@~22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.0.0.tgz#3295c6992c70089c4b8f5cf060489fddf7ee9816" + integrity sha512-p5ZTEb5h+O+iU02t0GfEjAnkdYPrQSkfuTSMkMYyIoMvUNEHsbG0bHHbfXIcfTqD2UfvjQX7mmgiFsyRwGscVw== dependencies: abab "^2.0.6" - acorn "^8.8.2" - acorn-globals "^7.0.0" cssstyle "^3.0.0" data-urls "^4.0.0" decimal.js "^10.4.3" domexception "^4.0.0" - escodegen "^2.0.0" form-data "^4.0.0" html-encoding-sniffer "^3.0.0" http-proxy-agent "^5.0.0" https-proxy-agent "^5.0.1" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.2" + nwsapi "^2.2.4" parse5 "^7.1.2" rrweb-cssom "^0.6.0" saxes "^6.0.0" @@ -6937,21 +6786,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-buffer@3.0.1, json-buffer@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -7025,13 +6864,6 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - keyv@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" @@ -7107,14 +6939,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libphonenumber-js@^1.6.4: - version "1.7.22" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.7.22.tgz#e206e3c227ec47ff306f7405ac52c331911e1b35" - integrity sha512-KCK4874gp640B+d1zjNi2ZiZ3ubEUPH/0B9AwcuhHDVAL6NdthxC8obbdhGlo7FcEpjFVwEMDoHRfi+lI8NM/g== - dependencies: - minimist "^1.2.0" - xml2js "^0.4.17" - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -7139,16 +6963,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -7252,7 +7066,7 @@ lodash@4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.15.0, lodash@^4.17.14, lodash@^4.17.5, lodash@~4.17.11, lodash@~4.17.14: +lodash@^4.15.0, lodash@^4.17.14, lodash@~4.17.11, lodash@~4.17.14: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -7291,7 +7105,7 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: +lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== @@ -7406,11 +7220,6 @@ memoize-one@~6.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -7435,14 +7244,14 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== -metascraper-audio@^5.33.5: - version "5.33.5" - resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.33.5.tgz#5400fab5154636f166aff10e205a429c9e75d9a2" - integrity sha512-zO2PyRIVH10RTZlphYXVOAn8U17ZaH/JkY95h1BJ9eMBWfnziuWCtN0tIUUysQ+Ci8vcci65tasYqdG7jfcHKQ== +metascraper-audio@^5.34.4: + version "5.34.4" + resolved "https://registry.yarnpkg.com/metascraper-audio/-/metascraper-audio-5.34.4.tgz#84437c3962a9186cda69510229f6b68e6a25563a" + integrity sha512-Z/BzT0KLq8H6Zc0wm4PqKlSeI7O54ZsD2QhV9GgkJNz5jGj0Rs4P3OOvoa+FEtDyRKGfIzYuldAuhQMVmo1WMA== dependencies: - "@keyvhq/memoize" "~1.6.14" - "@metascraper/helpers" "^5.33.5" - got "~11.8.5" + "@keyvhq/memoize" "~2.0.0" + "@metascraper/helpers" "^5.34.4" + got "~11.8.6" p-reflect "~2.1.0" metascraper-author@^5.33.5: @@ -7452,13 +7261,6 @@ metascraper-author@^5.33.5: dependencies: "@metascraper/helpers" "^5.33.5" -metascraper-clearbit-logo@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/metascraper-clearbit-logo/-/metascraper-clearbit-logo-5.3.0.tgz#e4596dbaf35503635d585a705f4119c7eddc6773" - integrity sha512-Vg9m8cj2yEIUDrRjvCORvf8vgq2HLsWLVqNnIactNUPJ+Lcly5Xcxh5TcmHYp5Qx5DtNUCoKlakomYHn8mOjqg== - dependencies: - got "~9.6.0" - metascraper-date@^5.33.5: version "5.33.5" resolved "https://registry.yarnpkg.com/metascraper-date/-/metascraper-date-5.33.5.tgz#fccd8f7b685e0f3bb152c86aa93ed03a96c3c834" @@ -7511,12 +7313,12 @@ metascraper-publisher@^5.33.5: dependencies: "@metascraper/helpers" "^5.33.5" -metascraper-soundcloud@^5.34.2: - version "5.34.2" - resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.34.2.tgz#ee1077b4836321ccfb22bcab4bfae7360dd571a3" - integrity sha512-1VafeFnlzJaYUlq8XbppQauz0i9xM8QycJU8k4ONftZwRtikQBIjRe5BMXr/s5n32831vBooRz8ksz0CXkjVlQ== +metascraper-soundcloud@^5.34.4: + version "5.34.4" + resolved "https://registry.yarnpkg.com/metascraper-soundcloud/-/metascraper-soundcloud-5.34.4.tgz#f5e154e20ddad8902630b6f128f0928e7475ef83" + integrity sha512-EO17QYd7HOEGFHgKjC68x5fp8j+V+iYeGO0IgWY53olOfIrxBMG6gG1IIqcOjhbZFVHNX1uBz4BVmNtTvHvcjw== dependencies: - "@metascraper/helpers" "^5.34.2" + "@metascraper/helpers" "^5.34.4" metascraper-title@^5.33.5: version "5.33.5" @@ -7563,7 +7365,7 @@ metascraper@^5.33.5: lodash "~4.17.21" whoops "~4.1.2" -methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -7638,7 +7440,7 @@ mime-types@^2.1.26, mime-types@~2.1.22, mime-types@~2.1.24: dependencies: mime-db "1.43.0" -mime@1.6.0, mime@^1.4.1: +mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -7663,7 +7465,7 @@ mimic-fn@~3.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== -mimic-response@^1.0.0, mimic-response@^1.0.1: +mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== @@ -7812,11 +7614,6 @@ moment@2.21.0: resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== -moment@^2.22.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8116,11 +7913,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee" - integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ== - normalize-url@^6.0.1, normalize-url@~6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" @@ -8144,21 +7936,6 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" -npm-run-all@~4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -8212,15 +7989,10 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -numeral@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" - integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= - -nwsapi@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" - integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== +nwsapi@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5" + integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g== oauth-sign@~0.9.0: version "0.9.0" @@ -8339,7 +8111,7 @@ optimism@^0.10.0: dependencies: "@wry/context" "^0.4.0" -optionator@^0.8.1, optionator@^0.8.3: +optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -8369,11 +8141,6 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-cancelable@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" @@ -8493,14 +8260,6 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -8618,13 +8377,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -8650,11 +8402,6 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pidtree@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" - integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== - pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8727,11 +8474,6 @@ prepend-http@^1.0.1: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -8859,11 +8601,6 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.5.1: - version "6.8.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081" - integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w== - qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -8972,16 +8709,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.5: +readable-stream@^2.0.2, readable-stream@^2.0.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -9183,23 +8911,7 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== - dependencies: - lodash "^4.17.15" - -request-promise-native@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== - dependencies: - request-promise-core "1.1.3" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.88.0, request@~2.88.2: +request@~2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -9283,13 +8995,6 @@ resolve@^1.20.0, resolve@^1.3.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - responselike@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" @@ -9368,7 +9073,7 @@ run-parallel@^1.1.9: resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== -rxjs@^6.4.0, rxjs@^6.5.4: +rxjs@^6.4.0: version "6.5.4" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== @@ -9587,11 +9292,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.1.tgz#3161d969886fb14f9140c65245a5dd19b6f0b06b" - integrity sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg== - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -9737,7 +9437,7 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -9866,11 +9566,6 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g== - streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" @@ -9924,15 +9619,6 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.padend@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" - integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA= - dependencies: - define-properties "^1.1.2" - es-abstract "^1.4.3" - function-bind "^1.0.2" - string.prototype.trimend@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" @@ -10053,30 +9739,6 @@ subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16, subscrip symbol-observable "^1.0.4" ws "^5.2.0 || ^6.0.0 || ^7.0.0" -superagent@^3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" - integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== - dependencies: - component-emitter "^1.2.0" - cookiejar "^2.1.0" - debug "^3.1.0" - extend "^3.0.0" - form-data "^2.3.1" - formidable "^1.2.0" - methods "^1.1.1" - mime "^1.4.1" - qs "^6.5.1" - readable-stream "^2.3.5" - -supertest@~4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36" - integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ== - dependencies: - methods "^1.1.2" - superagent "^3.8.3" - supports-color@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" @@ -10281,11 +9943,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -10328,14 +9985,6 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -10346,6 +9995,14 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" @@ -10616,13 +10273,6 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" @@ -10741,10 +10391,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-13.0.0.tgz#0fb6c6bb5218ea23d368a8347e6d0f5a70e3bcab" - integrity sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA== +validator@^13.9.0: + version "13.9.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855" + integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA== vary@^1, vary@~1.1.2: version "1.1.2" @@ -10777,18 +10427,6 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -wait-on@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-4.0.1.tgz#c49ca18b1ea60580404feed9df76ab3af2425a56" - integrity sha512-x83fmTH2X0KL7vXoGt9aV5x4SMCvO8A/NbwWpaYYh4NJ16d3KSgbHwBy9dVdHj0B30cEhOFRvDob4fnpUmZxvA== - dependencies: - "@hapi/joi" "^17.1.0" - lodash "^4.17.15" - minimist "^1.2.0" - request "^2.88.0" - request-promise-native "^1.0.8" - rxjs "^6.5.4" - walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -10944,7 +10582,7 @@ xml-name-validator@^4.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== -xml2js@0.4.19, xml2js@^0.4.17: +xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== diff --git a/deployment/README.deployment.md b/deployment/README.deployment.md index 5335c11ad..f085b48d0 100644 --- a/deployment/README.deployment.md +++ b/deployment/README.deployment.md @@ -17,7 +17,62 @@ You have the following options for a deployment: After the first deployment of the new network on your server, the database is initialized with the default administrator: -- E-mail: admin@example.org -- Password: 1234 +- E-mail: `admin@example.org` +- Password: `1234` ***ATTENTION:*** When you are logged in for the first time, please change your (the admin's) e-mail to an existing one and change your password to a secure one !!! + +## Use the Scripts + +To use all the scripts you have to set the variable `CONFIGURATION` in your terminal by entering: + +```bash +# in deployment folder + +# set configuration name to folder name in 'configurations' folder (network name) +$ export CONFIGURATION=`` +# to check this +$ echo $CONFIGURATION +``` + +### Secrets Encrypt/Decrypt + +To encrypt and decrypt the secrets of your network in your terminal set a correct password in a (new) file `configurations//SECRET`. +If done please enter: + +```bash +# in deployment folder + +# encrypt secrets +$ scripts/secrets.encrypt.sh + +# decrypt secrets +$ scripts/secrets.decrypt.sh +``` + +### Maintenance Mode On/Off + +Activate or deactivate maintenance mode in your terminal: + +```bash +# in deployment folder + +# activate maintenance mode +$ scripts/cluster.maintenance.sh on + +# deactivate maintenance mode +$ scripts/cluster.maintenance.sh off +``` + +### Backup Script + +To save a locale backup of the database and uploaded images: + +```bash +# in deployment folder + +# save backup +$ scripts/cluster.backup.sh +``` + +The backup will be saved into your network folders `backup` folder in a new folder with the date and time. diff --git a/deployment/TODO-next-update.md b/deployment/TODO-next-update.md index 769fe4d67..8630275b7 100644 --- a/deployment/TODO-next-update.md +++ b/deployment/TODO-next-update.md @@ -2,7 +2,9 @@ When you overtake this deploy and rebrand repo to your network you have to recognize the following changes and doings: -## Version >= 2.4.0 with 'ocelotDockerVersionTag' 2.4.0-XXX +## Version >= 2.4.0 with 'ocelotDockerVersionTag' 2.4.0-298 + +- You have to set `SHOW_CONTENT_FILTER_HEADER_MENU` and `SHOW_CONTENT_FILTER_MASONRY_GRID` in `branding/constants/filter.js` originally in main code file `webapp/constants/filter.js` to your preferred value. ### Main Code PR – feat(webapp): map #5843 diff --git a/package.json b/package.json index a1a6336df..bc2cdfd62 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,9 @@ "@babel/core": "^7.21.8", "@babel/preset-env": "^7.21.5", "@babel/register": "^7.12.10", - "@faker-js/faker": "5.1.0", + "@faker-js/faker": "7.6.0", "auto-changelog": "^2.3.0", "bcryptjs": "^2.4.3", - "codecov": "^3.8.2", "cross-env": "^7.0.3", "cucumber": "^6.0.5", "cypress": "^7.0.1", @@ -43,13 +42,12 @@ "mock-socket": "^9.0.3", "neo4j-driver": "^4.3.4", "neode": "^0.4.8", - "npm-run-all": "^4.1.5", "rosie": "^2.1.0", - "slug": "^6.0.0" + "slug": "^6.0.0", + "wait-on": "^7.0.1" }, "resolutions": { "set-value": "^2.0.1", "nan": "2.17.0" - }, - "dependencies": {} + } } diff --git a/webapp/.eslintignore b/webapp/.eslintignore index be90fc8e3..a90a66efa 100644 --- a/webapp/.eslintignore +++ b/webapp/.eslintignore @@ -1,5 +1,5 @@ node_modules -build +dist .nuxt styleguide/ **/*.min.js diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 5bebf9542..b0ecbdd37 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -1,7 +1,7 @@ ################################################################################## # BASE (Is pushed to DockerHub for rebranding) ################################### ################################################################################## -FROM node:20.1.0-alpine3.17 as base +FROM node:20.2.0-alpine3.17 as base # ENVs ## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame diff --git a/webapp/Dockerfile.maintenance b/webapp/Dockerfile.maintenance index dbd7d9bac..a6db9e612 100644 --- a/webapp/Dockerfile.maintenance +++ b/webapp/Dockerfile.maintenance @@ -1,7 +1,7 @@ ################################################################################## # BASE ########################################################################### ################################################################################## -FROM node:20.1.0-alpine3.17 as base +FROM node:20.2.0-alpine3.17 as base # ENVs ## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame diff --git a/webapp/assets/_new/icons/svgs/book.svg b/webapp/assets/_new/icons/svgs/book.svg new file mode 100644 index 000000000..305e367ac --- /dev/null +++ b/webapp/assets/_new/icons/svgs/book.svg @@ -0,0 +1,5 @@ + + +book + + diff --git a/webapp/assets/_new/icons/svgs/calendar.svg b/webapp/assets/_new/icons/svgs/calendar.svg new file mode 100644 index 000000000..5a67a8299 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/calendar.svg @@ -0,0 +1,5 @@ + + +calendar + + diff --git a/webapp/components/ContributionForm/ContributionForm.spec.js b/webapp/components/ContributionForm/ContributionForm.spec.js index ef3b47c37..6b9db448b 100644 --- a/webapp/components/ContributionForm/ContributionForm.spec.js +++ b/webapp/components/ContributionForm/ContributionForm.spec.js @@ -15,6 +15,7 @@ const stubs = { 'client-only': true, 'nuxt-link': true, 'v-popover': true, + 'date-picker': true, } describe('ContributionForm.vue', () => { @@ -45,6 +46,7 @@ describe('ContributionForm.vue', () => { slug: 'this-is-a-title-for-a-post', content: postContent, contentExcerpt: postContent, + postType: ['Article'], }, }, }), @@ -142,6 +144,7 @@ describe('ContributionForm.vue', () => { id: null, image: null, groupId: null, + postType: 'Article', }, } postTitleInput = wrapper.find('.ds-input') @@ -268,6 +271,7 @@ describe('ContributionForm.vue', () => { image: { sensitive: false, }, + postType: 'Article', }, } }) diff --git a/webapp/components/ContributionForm/ContributionForm.vue b/webapp/components/ContributionForm/ContributionForm.vue index f740e1f05..997a25341 100644 --- a/webapp/components/ContributionForm/ContributionForm.vue +++ b/webapp/components/ContributionForm/ContributionForm.vue @@ -30,6 +30,7 @@ + + + +
+
+ + + + +
+ +
+
+ + + +
+
+ + + + + +
+ + + +
+ + {{ formData.eventVenue.length }}/{{ formSchema.eventVenue.max }} + + +
+
+ + +
+ + {{ formData.eventLocationName.length }}/{{ formSchema.eventLocationName.max }} + + +
+
+
+ +
+ + {{ $t('post.viewEvent.eventIsOnline') }} +
+
+ + - diff --git a/webapp/components/DonationInfo/DonationInfo.vue b/webapp/components/DonationInfo/DonationInfo.vue index b66f33305..00b9bf584 100644 --- a/webapp/components/DonationInfo/DonationInfo.vue +++ b/webapp/components/DonationInfo/DonationInfo.vue @@ -47,5 +47,6 @@ export default { display: flex; flex: 1; margin-bottom: $space-x-small; + margin-top: 16px; } diff --git a/webapp/components/Editor/nodes/Mention.js b/webapp/components/Editor/nodes/Mention.js index dad983ed6..fa1555877 100644 --- a/webapp/components/Editor/nodes/Mention.js +++ b/webapp/components/Editor/nodes/Mention.js @@ -17,7 +17,7 @@ export default class Mention extends TipTapMention { 'data-mention-id': node.attrs.id, target: '_blank', }, - `${this.options.matcher.char}${node.attrs.label}`, + `${this.options.matcher.char}${node.attrs.label} `, ] }, parseDOM: [ diff --git a/webapp/components/FilterMenu/CategoriesFilter.spec.js b/webapp/components/FilterMenu/CategoriesFilter.spec.js index 677217585..823f0ecb0 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.spec.js +++ b/webapp/components/FilterMenu/CategoriesFilter.spec.js @@ -72,9 +72,7 @@ describe('CategoriesFilter.vue', () => { describe('click on an "catetories-buttons" button', () => { it('calls TOGGLE_CATEGORY when clicked', () => { - environmentAndNatureButton = wrapper - .findAll('.categories-filter .item-category .base-button') - .at(0) + environmentAndNatureButton = wrapper.findAll('.category-filter-list .base-button').at(0) environmentAndNatureButton.trigger('click') expect(mutations['posts/TOGGLE_CATEGORY']).toHaveBeenCalledWith({}, 'cat4') }) diff --git a/webapp/components/FilterMenu/CategoriesFilter.vue b/webapp/components/FilterMenu/CategoriesFilter.vue index bcbfc7d1c..8b96245fd 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.vue +++ b/webapp/components/FilterMenu/CategoriesFilter.vue @@ -17,18 +17,22 @@ @@ -95,3 +99,13 @@ export default { }, } + diff --git a/webapp/components/FilterMenu/FilterMenu.spec.js b/webapp/components/FilterMenu/FilterMenu.spec.js index 6e9741e79..c3a3ca113 100644 --- a/webapp/components/FilterMenu/FilterMenu.spec.js +++ b/webapp/components/FilterMenu/FilterMenu.spec.js @@ -20,6 +20,7 @@ describe('FilterMenu.vue', () => { const stubs = { FollowingFilter: true, + PostTypeFilter: true, CategoriesFilter: true, EmotionsFilter: true, LanguagesFilter: true, diff --git a/webapp/components/FilterMenu/FilterMenuComponent.vue b/webapp/components/FilterMenu/FilterMenuComponent.vue index 7a582a7c4..708c3bc7c 100644 --- a/webapp/components/FilterMenu/FilterMenuComponent.vue +++ b/webapp/components/FilterMenu/FilterMenuComponent.vue @@ -3,6 +3,7 @@

{{ $t('filter-menu.filter-by') }}

+
@@ -13,6 +14,7 @@ diff --git a/webapp/components/PostTeaser/PostTeaser.spec.js b/webapp/components/PostTeaser/PostTeaser.spec.js index 7791c62f0..0d48729d3 100644 --- a/webapp/components/PostTeaser/PostTeaser.spec.js +++ b/webapp/components/PostTeaser/PostTeaser.spec.js @@ -28,6 +28,7 @@ describe('PostTeaser', () => { author: { id: 'u1', }, + postType: ['Article'], }, } stubs = { diff --git a/webapp/components/PostTeaser/PostTeaser.vue b/webapp/components/PostTeaser/PostTeaser.vue index 228c4c90c..604fb121f 100644 --- a/webapp/components/PostTeaser/PostTeaser.vue +++ b/webapp/components/PostTeaser/PostTeaser.vue @@ -11,6 +11,7 @@ }" :highlight="isPinned" > + @@ -19,11 +20,37 @@

{{ post.title }}

+ + + + + + + {{ $t('post.viewEvent.eventIsOnline') }} + + + {{ post.eventLocationName }} + + + + + + + {{ getEventDateString }} + + + +
@@ -91,6 +118,7 @@ import UserTeaser from '~/components/UserTeaser/UserTeaser' import { mapGetters } from 'vuex' import PostMutations from '~/graphql/PostMutations' import { postMenuModalsData, deletePostMutation } from '~/components/utils/PostHelpers' +import { format } from 'date-fns' export default { name: 'PostTeaser', @@ -152,6 +180,20 @@ export default { isPinned() { return this.post && this.post.pinned }, + ribbonText() { + if (this.post.pinned) return this.$t('post.pinned') + if (this.post.postType[0] === 'Event') return this.$t('post.event') + return this.$t('post.name') + }, + getEventDateString() { + if (this.post.eventEnd) { + const eventStart = format(new Date(this.post.eventStart), 'dd.MM.') + const eventEnd = format(new Date(this.post.eventEnd), 'dd.MM.yyyy') + return `${eventStart} - ${eventEnd}` + } else { + return format(new Date(this.post.eventStart), 'dd.MM.yyyy') + } + }, }, methods: { async deletePostCallback() { @@ -194,6 +236,7 @@ export default { display: block; height: 100%; color: $text-color-base; + padding-top: 16px; } .post-user-row { @@ -235,6 +278,12 @@ export default { margin-bottom: $space-small; } + & .event-info { + display: flex; + align-items: center; + gap: 2px; + } + > .footer { display: flex; justify-content: space-between; diff --git a/webapp/components/Ribbon/index.vue b/webapp/components/Ribbon/index.vue index f54b456e5..ea7fd273a 100644 --- a/webapp/components/Ribbon/index.vue +++ b/webapp/components/Ribbon/index.vue @@ -1,5 +1,5 @@ @@ -12,6 +12,10 @@ export default { type: String, default: '', }, + typ: { + type: String, + default: 'blue', + }, }, } @@ -43,4 +47,11 @@ export default { } } } +.eventBg { + background-color: $color-success-active; + + &::before { + border-color: $color-success-active transparent transparent $color-success-active; + } +} diff --git a/webapp/graphql/PostMutations.js b/webapp/graphql/PostMutations.js index 8880a93b0..6b71b86b9 100644 --- a/webapp/graphql/PostMutations.js +++ b/webapp/graphql/PostMutations.js @@ -4,21 +4,30 @@ export default () => { return { CreatePost: gql` mutation ( + $id: ID $title: String! + $slug: String $content: String! $categoryIds: [ID] $image: ImageInput $groupId: ID + $postType: PostType + $eventInput: _EventInput ) { CreatePost( + id: $id title: $title + slug: $slug content: $content categoryIds: $categoryIds image: $image groupId: $groupId + postType: $postType + eventInput: $eventInput ) { - title + id slug + title content contentExcerpt language @@ -26,6 +35,22 @@ export default () => { url sensitive } + disabled + deleted + postType + author { + name + } + categories { + id + } + eventStart + eventVenue + eventLocationName + eventLocation { + lng + lat + } } } `, diff --git a/webapp/graphql/PostQuery.js b/webapp/graphql/PostQuery.js index 1c6c76276..83eec2a30 100644 --- a/webapp/graphql/PostQuery.js +++ b/webapp/graphql/PostQuery.js @@ -24,6 +24,12 @@ export default (i18n) => { query Post($id: ID!) { Post(id: $id) { + postType + eventStart + eventEnd + eventVenue + eventLocationName + eventIsOnline ...post ...postCounts ...tagsCategoriesAndPinned @@ -66,6 +72,12 @@ export const filterPosts = (i18n) => { query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) { Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { + postType + eventStart + eventEnd + eventVenue + eventLocationName + eventIsOnline ...post ...postCounts ...tagsCategoriesAndPinned @@ -103,6 +115,10 @@ export const profilePagePosts = (i18n) => { $orderBy: [_PostOrdering] ) { profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { + postType + eventStart + eventVenue + eventLocationName ...post ...postCounts ...tagsCategoriesAndPinned diff --git a/webapp/jest.config.js b/webapp/jest.config.js index 6b0f5e461..b38b05ecd 100644 --- a/webapp/jest.config.js +++ b/webapp/jest.config.js @@ -29,8 +29,9 @@ module.exports = { modulePathIgnorePatterns: ['/dist/'], moduleNameMapper: { '\\.(svg)$': '/test/fileMock.js', - '\\.(css|less)$': 'identity-obj-proxy', + '\\.(scss|css|less)$': 'identity-obj-proxy', '@mapbox/mapbox-gl-geocoder': 'identity-obj-proxy', + 'vue2-datepicker/locale/undefined': 'vue2-datepicker/locale/en', '^@/(.*)$': '/src/$1', '^~/(.*)$': '/$1', }, diff --git a/webapp/locales/de.json b/webapp/locales/de.json index fe39eed69..82b7c7bd4 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -278,6 +278,7 @@ "inappropriatePicture": "Dieses Bild kann für einige Menschen unangemessen sein.", "languageSelectLabel": "Sprache Deines Beitrags", "languageSelectText": "Sprache wählen", + "newEvent": "Erstelle einen neue Veranstaltung", "newPost": "Erstelle einen neuen Beitrag", "success": "Gespeichert!", "teaserImage": { @@ -377,9 +378,11 @@ }, "filter-menu": { "all": "Alle", + "article": "Artikel", "categories": "Themen", "deleteFilter": "Filter löschen", "emotions": "Emotionen", + "events": "Veranstaltungen", "filter-by": "Filtern nach ...", "following": "Nutzer denen ich folge", "languages": "Sprachen", @@ -395,6 +398,7 @@ } }, "order-by": "Sortieren nach ...", + "post-type": "Beitrags-Typ", "save": { "error": "Themen konnten nicht gespeichert werden!", "success": "Themen gespeichert!" @@ -684,6 +688,12 @@ "submitted": "Kommentar gesendet", "updated": "Änderungen gespeichert" }, + "createNewEvent": { + "forGroup": { + "title": "Für die Gruppe „{name}“" + }, + "title": "Erstelle ein neues Event" + }, "createNewPost": { "forGroup": { "title": "Für die Gruppe „{name}“" @@ -697,6 +707,7 @@ }, "title": "Bearbeite deinen Beitrag" }, + "event": "Veranstaltung", "menu": { "delete": "Beitrag löschen", "edit": "Beitrag bearbeiten", @@ -710,6 +721,14 @@ "takeAction": { "name": "Aktiv werden" }, + "viewEvent": { + "eventEnd": "Ende", + "eventIsOnline": "Online Veranstaltung", + "eventLocationName": "Stadt", + "eventStart": "Beginn", + "eventVenue": "Veranstaltungsort", + "title": "Veranstaltung" + }, "viewPost": { "forGroup": { "title": "In der Gruppe „{name}“" diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 92f5687c2..45a241957 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -278,6 +278,7 @@ "inappropriatePicture": "This image may be inappropriate for some people.", "languageSelectLabel": "Language of your contribution", "languageSelectText": "Select Language", + "newEvent": "Create a new Event", "newPost": "Create a new Post", "success": "Saved!", "teaserImage": { @@ -377,9 +378,11 @@ }, "filter-menu": { "all": "All", + "article": "Article", "categories": "Topics", "deleteFilter": "Delete filter", "emotions": "Emotions", + "events": "Events", "filter-by": "Filter by ...", "following": "Users I follow", "languages": "Languages", @@ -395,6 +398,7 @@ } }, "order-by": "Order by ...", + "post-type": "Post type", "save": { "error": "Failed saving topic settings!", "success": "Topics saved!" @@ -684,6 +688,12 @@ "submitted": "Comment submitted!", "updated": "Changes saved!" }, + "createNewEvent": { + "forGroup": { + "title": "For The Group “{name}”" + }, + "title": "Create A New Event" + }, "createNewPost": { "forGroup": { "title": "For The Group “{name}”" @@ -697,6 +707,7 @@ }, "title": "Edit Your Post" }, + "event": "Event", "menu": { "delete": "Delete post", "edit": "Edit post", @@ -710,6 +721,14 @@ "takeAction": { "name": "Take action" }, + "viewEvent": { + "eventEnd": "End", + "eventIsOnline": "Online Event", + "eventLocationName": "City", + "eventStart": "Start", + "eventVenue": "Venue", + "title": "Event" + }, "viewPost": { "forGroup": { "title": "In The Group “{name}”" diff --git a/webapp/package.json b/webapp/package.json index 095cb12ee..9c0b91155 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -60,6 +60,7 @@ "vue-observe-visibility": "^1.0.0", "vue-scrollto": "^2.20.0", "vue-sweetalert-icons": "~4.3.1", + "vue2-datepicker": "^3.11.1", "vuex-i18n": "~1.13.1", "xregexp": "^4.3.0", "zxcvbn": "^4.4.2" diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index 7f5bdab1b..591da3300 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -18,14 +18,8 @@
- - - +
+
- - - - - +
+
+ +
+
- - - +
+
- +
+
+ +