Merge branch 'master' into update-database

This commit is contained in:
Hannes Heine 2023-05-31 21:26:39 +02:00 committed by GitHub
commit 41cd147258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1921 additions and 1135 deletions

View File

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

View File

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

View File

@ -1,3 +1,5 @@
DEBUG=true
NEO4J_URI=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=letmein

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
enum PostType {
Article
Event
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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=`<your-configuration-name>`
# 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/<your-configuration-name>/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.

View File

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

View File

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

View File

@ -1,5 +1,5 @@
node_modules
build
dist
.nuxt
styleguide/
**/*.min.js

View File

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

View File

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

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>book</title>
<path d="M10 5c2.92 0 5.482 0.981 6 1.188 0.518-0.206 3.080-1.188 6-1.188 3.227 0 6.375 1.313 6.375 1.313l0.625 0.281v20.406h-11.281c-0.346 0.597-0.979 1-1.719 1s-1.373-0.403-1.719-1h-11.281v-20.406l0.625-0.281s3.148-1.313 6.375-1.313zM10 7c-2.199 0-4.232 0.69-5 0.969v16.125c1.188-0.392 2.897-0.875 5-0.875 2.057 0 3.888 0.506 5 0.875v-16.125c-1-0.343-3.067-0.969-5-0.969zM22 7c-1.933 0-4 0.626-5 0.969v16.125c1.112-0.369 2.943-0.875 5-0.875 2.103 0 3.813 0.483 5 0.875v-16.125c-0.768-0.279-2.801-0.969-5-0.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 680 B

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>calendar</title>
<path d="M9 4h2v1h10v-1h2v1h4v22h-22v-22h4v-1zM7 7v2h18v-2h-2v1h-2v-1h-10v1h-2v-1h-2zM7 11v14h18v-14h-18zM13 13h2v2h-2v-2zM17 13h2v2h-2v-2zM21 13h2v2h-2v-2zM9 17h2v2h-2v-2zM13 17h2v2h-2v-2zM17 17h2v2h-2v-2zM21 17h2v2h-2v-2zM9 21h2v2h-2v-2zM13 21h2v2h-2v-2zM17 21h2v2h-2v-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 445 B

View File

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

View File

@ -30,6 +30,7 @@
<base-icon name="question-circle" />
</page-params-link>
</div>
<ds-space margin-top="base" />
<ds-input
model="title"
:placeholder="$t('contribution.title')"
@ -51,6 +52,93 @@
{{ contentLength }}
<base-icon v-if="errors && errors.content" name="warning" />
</ds-chip>
<!-- Eventdata -->
<div v-if="creatEvent" class="eventDatas">
<hr />
<ds-space margin-top="x-small" />
<ds-grid>
<ds-grid-item style="grid-row-end: span 3">
<!-- <label>Beginn</label> -->
<div style="z-index: 20">
<date-picker
name="eventStart"
v-model="formData.eventStart"
type="datetime"
value-type="format"
:minute-step="15"
Xformat="DD-MM-YYYY HH:mm"
style="z-index: 20"
:placeholder="$t('post.viewEvent.eventStart')"
:disabled-date="notBeforeToday"
:disabled-time="notBeforeNow"
:show-second="false"
></date-picker>
</div>
<div class="chipbox" style="margin-top: 10px">
<ds-chip size="base" :color="errors && errors.eventStart && 'danger'">
<base-icon v-if="errors && errors.eventStart" name="warning" />
</ds-chip>
</div>
</ds-grid-item>
<ds-grid-item style="grid-row-end: span 3">
<!-- <label>Ende (optional)</label> -->
<date-picker
v-model="formData.eventEnd"
type="datetime"
value-type="format"
:minute-step="15"
:seconds-step="0"
Xformat="DD-MM-YYYY HH:mm"
:placeholder="$t('post.viewEvent.eventEnd')"
style="font-size: larger"
:disabled-date="notBeforeEventDay"
:disabled-time="notBeforeEvent"
:show-second="false"
></date-picker>
</ds-grid-item>
</ds-grid>
<ds-grid>
<ds-grid-item style="grid-row-end: span 3">
<ds-input
model="eventVenue"
name="location"
:placeholder="$t('post.viewEvent.eventVenue')"
/>
<div class="chipbox">
<ds-chip size="base" :color="errors && errors.eventVenue && 'danger'">
{{ formData.eventVenue.length }}/{{ formSchema.eventVenue.max }}
<base-icon v-if="errors && errors.eventVenue" name="warning" />
</ds-chip>
</div>
</ds-grid-item>
<ds-grid-item style="grid-row-end: span 3">
<ds-input
model="eventLocationName"
name="venue"
:placeholder="$t('post.viewEvent.eventLocationName')"
/>
<div class="chipbox">
<ds-chip size="base" :color="errors && errors.eventLocationName && 'danger'">
{{ formData.eventLocationName.length }}/{{ formSchema.eventLocationName.max }}
<base-icon v-if="errors && errors.eventLocationName" name="warning" />
</ds-chip>
</div>
</ds-grid-item>
</ds-grid>
<div>
<input
type="checkbox"
model="formData.eventIsOnline"
name="eventIsOnline"
style="font-size: larger"
/>
{{ $t('post.viewEvent.eventIsOnline') }}
</div>
</div>
<ds-space margin-top="x-small" />
<categories-select
v-if="categoriesActive"
model="categoryIds"
@ -67,6 +155,7 @@
<ds-flex class="buttons-footer" gutter="xxx-small">
<ds-flex-item width="3.5" style="margin-right: 16px; margin-bottom: 6px">
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- TODO => remove v-html! only text ! no html! security first! -->
<ds-text
v-if="showGroupHint"
v-html="$t('contribution.visibleOnlyForMembersOfGroup', { name: groupName })"
@ -92,7 +181,6 @@
</template>
</ds-form>
</template>
<script>
import gql from 'graphql-tag'
import { mapGetters } from 'vuex'
@ -102,6 +190,8 @@ import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
import ImageUploader from '~/components/Uploader/ImageUploader'
import links from '~/constants/links.js'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
import DatePicker from 'vue2-datepicker'
import 'vue2-datepicker/scss/index.scss'
export default {
components: {
@ -109,6 +199,7 @@ export default {
ImageUploader,
PageParamsLink,
CategoriesSelect,
DatePicker,
},
props: {
contribution: {
@ -119,15 +210,30 @@ export default {
type: Object,
default: () => null,
},
creatEvent: {
type: Boolean,
default: false,
},
},
data() {
const { title, content, image, categories } = this.contribution
const {
title,
content,
image,
categories,
eventStart,
eventEnd,
eventLocationName,
eventVenue,
eventIsOnline,
eventLocation,
} = this.contribution
const {
sensitive: imageBlurred = false,
aspectRatio: imageAspectRatio = null,
type: imageType = null,
} = image || {}
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
links,
@ -139,6 +245,12 @@ export default {
imageType,
imageBlurred,
categoryIds: categories ? categories.map((category) => category.id) : [],
eventStart: eventStart || null,
eventEnd: eventEnd || null,
eventLocation: eventLocation || '',
eventLocationName: eventLocationName || '',
eventVenue: eventVenue || '',
eventIsOnline: eventIsOnline || false,
},
formSchema: {
title: { required: true, min: 3, max: 100 },
@ -154,6 +266,9 @@ export default {
return []
},
},
eventStart: { required: !!this.creatEvent },
eventVenue: { required: !!this.creatEvent, min: 3, max: 100 },
eventLocationName: { required: !!this.creatEvent, min: 3, max: 100 },
},
loading: false,
users: [],
@ -161,10 +276,25 @@ export default {
imageUpload: null,
}
},
async mounted() {
await import(`vue2-datepicker/locale/${this.currentUser.locale}`)
},
computed: {
...mapGetters({
currentUser: 'auth/user',
}),
eventInput() {
if (this.creatEvent) {
return {
eventStart: this.formData.eventStart,
eventVenue: this.formData.eventVenue,
eventEnd: this.formData.eventEnd,
eventIsOnline: this.formData.eventIsOnline,
eventLocationName: this.formData.eventLocationName,
}
}
return undefined
},
contentLength() {
return this.$filters.removeHtml(this.formData.content).length
},
@ -188,8 +318,21 @@ export default {
},
},
methods: {
notBeforeToday(date) {
return date < new Date().setHours(0, 0, 0, 0)
},
notBeforeNow(date) {
return date < new Date()
},
notBeforeEventDay(date) {
return date < new Date(this.formData.eventStart).setHours(0, 0, 0, 0)
},
notBeforeEvent(date) {
return date <= new Date(this.formData.eventStart)
},
submit() {
let image = null
const { title, content, categoryIds } = this.formData
if (this.formData.image) {
image = {
@ -202,6 +345,7 @@ export default {
}
}
this.loading = true
this.$apollo
.mutate({
mutation: this.contribution.id ? PostMutations().UpdatePost : PostMutations().CreatePost,
@ -212,6 +356,8 @@ export default {
id: this.contribution.id || null,
image,
groupId: this.groupId,
postType: !this.creatEvent ? 'Article' : 'Event',
eventInput: this.eventInput,
},
})
.then(({ data }) => {
@ -288,6 +434,17 @@ export default {
</script>
<style lang="scss">
.eventDatas {
.chipbox {
display: flex;
justify-content: flex-end;
> .ds-chip {
margin-top: -10px;
}
}
}
.contribution-form > .base-card {
display: flex;
flex-direction: column;
@ -369,5 +526,27 @@ export default {
}
}
}
.mx-datepicker {
width: 100%;
}
.mx-datepicker input {
font-size: 1rem;
height: calc(1.625rem + 18px);
padding: 8px 8px;
background-color: #faf9fa;
border-color: #c8c8c8;
color: #4b4554;
}
.mx-datepicker input:hover {
border-color: #c8c8c8;
}
.mx-datepicker input:focus {
border-color: #17b53f;
background-color: #fff;
}
.mx-datepicker-error {
border-color: #cf2619;
}
}
</style>

View File

@ -47,5 +47,6 @@ export default {
display: flex;
flex: 1;
margin-bottom: $space-x-small;
margin-top: 16px;
}
</style>

View File

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

View File

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

View File

@ -17,18 +17,22 @@
</template>
<template #filter-list>
<li v-for="category in categories" :key="category.id" class="item item-category">
<labeled-button
:icon="category.icon"
:filled="filteredCategoryIds.includes(category.id)"
:label="$t(`contribution.category.name.${category.slug}`)"
<div class="category-filter-list">
<base-button
v-for="category in categories"
:key="category.id"
@click="toggleCategory(category.id)"
:filled="filteredCategoryIds.includes(category.id)"
:icon="category.icon"
size="small"
v-tooltip="{
content: $t(`contribution.category.description.${category.slug}`),
placement: 'bottom-start',
}"
/>
</li>
>
{{ $t(`contribution.category.name.${category.slug}`) }}
</base-button>
</div>
</template>
</filter-menu-section>
</template>
@ -95,3 +99,13 @@ export default {
},
}
</script>
<style lang="scss">
.category-filter-list {
margin-left: $space-xx-large;
> .base-button {
margin-right: $space-xx-small;
margin-bottom: $space-xx-small;
}
}
</style>

View File

@ -20,6 +20,7 @@ describe('FilterMenu.vue', () => {
const stubs = {
FollowingFilter: true,
PostTypeFilter: true,
CategoriesFilter: true,
EmotionsFilter: true,
LanguagesFilter: true,

View File

@ -3,6 +3,7 @@
<div class="filter-menu-options">
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
<following-filter />
<post-type-filter />
<categories-filter v-if="categoriesActive" @showFilterMenu="$emit('showFilterMenu')" />
</div>
<div class="filter-menu-options">
@ -13,6 +14,7 @@
</template>
<script>
import PostTypeFilter from './PostTypeFilter'
import FollowingFilter from './FollowingFilter'
import OrderByFilter from './OrderByFilter'
import CategoriesFilter from './CategoriesFilter'
@ -22,6 +24,7 @@ export default {
FollowingFilter,
OrderByFilter,
CategoriesFilter,
PostTypeFilter,
},
data() {
return {

View File

@ -0,0 +1,59 @@
<template>
<filter-menu-section
:title="$t('filter-menu.post-type')"
:divider="false"
class="following-filter"
>
<template #filter-follower>
<li class="item article-item">
<labeled-button
icon="book"
:label="$t('filter-menu.article')"
:filled="articleSet"
:title="$t('filter-menu.article')"
@click="toggleFilterPostType('Article')"
/>
</li>
<li class="item event-item">
<labeled-button
icon="calendar"
:label="$t('filter-menu.events')"
:filled="eventSet"
:title="$t('filter-menu.events')"
@click="toggleFilterPostType('Event')"
/>
</li>
</template>
</filter-menu-section>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
name: 'PostTypeFilter',
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes',
currentUser: 'auth/user',
}),
articleSet() {
return this.filteredPostTypes.includes('Article')
},
eventSet() {
return this.filteredPostTypes.includes('Event')
},
},
methods: {
...mapMutations({
toggleFilterPostType: 'posts/TOGGLE_POST_TYPE',
}),
},
}
</script>

View File

@ -28,6 +28,7 @@ describe('PostTeaser', () => {
author: {
id: 'u1',
},
postType: ['Article'],
},
}
stubs = {

View File

@ -11,6 +11,7 @@
}"
:highlight="isPinned"
>
<!-- {{ post }} -->
<template v-if="post.image" #heroImage>
<img :src="post.image | proxyApiUrl" class="image" />
</template>
@ -19,11 +20,37 @@
<user-teaser :user="post.author" :group="post.group" :date-time="post.createdAt" />
<hc-ribbon
:class="[isPinned ? '--pinned' : '', post.image ? 'post-ribbon-w-img' : 'post-ribbon']"
:text="isPinned ? $t('post.pinned') : $t('post.name')"
:text="ribbonText"
:typ="post.postType[0]"
/>
</div>
</client-only>
<h2 class="title hyphenate-text">{{ post.title }}</h2>
<ds-space
v-if="post && post.postType[0] === 'Event'"
margin-bottom="small"
style="padding: 5px"
>
<ds-flex>
<ds-flex-item>
<ds-text align="left" size="small" color="soft" class="event-info">
<base-icon name="map-marker" data-test="map-marker" />
<span v-if="post.eventIsOnline">
{{ $t('post.viewEvent.eventIsOnline') }}
</span>
<span v-else-if="post.eventLocationName">
{{ post.eventLocationName }}
</span>
</ds-text>
</ds-flex-item>
<ds-flex-item>
<ds-text align="left" color="soft" size="small" class="event-info">
<base-icon name="calendar" data-test="calendar" />
<span>{{ getEventDateString }}</span>
</ds-text>
</ds-flex-item>
</ds-flex>
</ds-space>
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="content hyphenate-text" v-html="excerpt" />
@ -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;

View File

@ -1,5 +1,5 @@
<template>
<aside class="ribbon">
<aside class="ribbon" :class="typ === 'Event' ? 'eventBg' : ''">
<p>{{ text }}</p>
</aside>
</template>
@ -12,6 +12,10 @@ export default {
type: String,
default: '',
},
typ: {
type: String,
default: 'blue',
},
},
}
</script>
@ -43,4 +47,11 @@ export default {
}
}
}
.eventBg {
background-color: $color-success-active;
&::before {
border-color: $color-success-active transparent transparent $color-success-active;
}
}
</style>

View File

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

View File

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

View File

@ -29,8 +29,9 @@ module.exports = {
modulePathIgnorePatterns: ['<rootDir>/dist/'],
moduleNameMapper: {
'\\.(svg)$': '<rootDir>/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',
'^@/(.*)$': '<rootDir>/src/$1',
'^~/(.*)$': '<rootDir>/$1',
},

View File

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

View File

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

View File

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

View File

@ -18,14 +18,8 @@
</nuxt-link>
</client-only>
</div>
<masonry-grid>
<!--Filter Button-->
<ds-grid-item
v-if="categoriesActive && SHOW_CONTENT_FILTER_MASONRY_GRID"
:row-span="1"
column-span="fullWidth"
class="top-filter-menu"
>
<div>
<div v-if="categoriesActive && SHOW_CONTENT_FILTER_MASONRY_GRID" class="top-filter-menu">
<div class="filterButtonBox">
<div class="filterButtonMenu" :class="{ 'hide-filter': hideByScroll }">
<base-button
@ -74,17 +68,24 @@
</div>
</div>
</div>
</ds-grid-item>
<!-- Placeholder/Space Row -->
<ds-grid-item :row-span="1" column-span="fullWidth" />
<!-- hashtag filter -->
<ds-grid-item v-if="hashtag" :row-span="2" column-span="fullWidth">
</div>
</div>
<div v-if="hashtag || showDonations" class="newsfeed-controls">
<div v-if="hashtag">
<hashtags-filter :hashtag="hashtag" @clearSearch="clearSearch" />
</ds-grid-item>
<!-- donation info -->
<ds-grid-item v-if="showDonations" class="top-info-bar" :row-span="1" column-span="fullWidth">
</div>
<div v-if="showDonations" class="top-info-bar">
<donation-info :goal="goal" :progress="progress" />
</ds-grid-item>
</div>
</div>
<!-- content grid -->
<masonry-grid
:class="[
!hashtag && !showDonations ? 'grid-margin-top' : '',
!isMobile && posts.length <= 2 ? 'grid-column-helper' : '',
]"
>
<!-- news feed -->
<template v-if="hasResults">
<masonry-grid-item
@ -133,6 +134,7 @@ import UpdateQuery from '~/components/utils/UpdateQuery'
import FilterMenuComponent from '~/components/FilterMenu/FilterMenuComponent'
import { SHOW_CONTENT_FILTER_MASONRY_GRID } from '~/constants/filter.js'
import { POST_ADD_BUTTON_POSITION_TOP } from '~/constants/posts.js'
import mobile from '~/mixins/mobile'
export default {
components: {
@ -145,7 +147,7 @@ export default {
FilterMenuComponent,
HeaderButton,
},
mixins: [postListActions],
mixins: [postListActions, mobile()],
data() {
const { hashtag = null } = this.$route.query
return {
@ -336,6 +338,10 @@ export default {
box-shadow: $box-shadow-x-large;
}
.top-filter-menu {
margin-top: 16px;
}
.top-info-bar,
.top-filter-menu {
display: flex;
@ -345,10 +351,17 @@ export default {
width: 95%;
position: fixed;
z-index: $z-index-sticky;
margin-top: -35px;
padding: 20px 10px 20px 10px;
margin-top: -45px;
padding: 30px 0px 20px 0px;
background-color: #f5f4f6;
}
.newsfeed-controls {
margin-top: 46px;
}
.main-container .grid-column-helper {
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 357px)) !important;
}
@media screen and (max-width: 656px) {
.filterButtonMenu {
margin-top: -50px;
@ -363,6 +376,9 @@ export default {
padding-bottom: 0px;
z-index: $z-index-page-submenu;
}
.grid-margin-top {
margin-top: 26px;
}
@media screen and (min-height: 401px) {
#my-filter {
max-height: 330px;
@ -412,8 +428,14 @@ export default {
width: 44px;
font-size: 23px;
}
.ds-grid {
padding-top: 1em;
}
@media screen and (max-width: 650px) {
// .top-filter-menu{
// margin-top: 24px;
// }
.newsfeed-controls {
margin-top: 32px;
}
}
</style>

View File

@ -19,6 +19,7 @@ describe('PostSlug', () => {
post: {
id: '1',
author,
postType: ['Article'],
comments: [
{
id: 'comment134',
@ -111,6 +112,7 @@ describe('PostSlug', () => {
id: '1',
author: null,
comments: [],
postType: ['Article'],
},
ready: true,
}

View File

@ -2,7 +2,7 @@
<transition name="fade" appear>
<div>
<ds-space margin="small">
<ds-heading tag="h1">{{ $t('post.viewPost.title') }}</ds-heading>
<ds-heading tag="h1">{{ heading }}</ds-heading>
<ds-heading v-if="post && post.group" tag="h2">
{{ $t('post.viewPost.forGroup.title', { name: post.group.name }) }}
</ds-heading>
@ -54,6 +54,33 @@
</section>
<ds-space margin-bottom="small" />
<h2 class="title hyphenate-text">{{ post.title }}</h2>
<!-- Eventdata -->
<ds-space
v-if="post && post.postType[0] === 'Event'"
margin-bottom="small"
style="padding: 10px"
>
<ds-text align="left" color="soft">
<base-icon name="map-marker" data-test="map-marker" />
<span v-if="post.eventVenue">{{ post.eventVenue }}</span>
<span v-if="!post.eventIsOnline">
<span v-if="post.eventVenue">-</span>
{{ post.eventLocationName }}
</span>
<span v-else>
<span v-if="post.eventVenue">-</span>
{{ $t('post.viewEvent.eventIsOnline') }}
</span>
</ds-text>
<ds-text align="left" color="soft" class="event-info">
<base-icon name="calendar" data-test="calendar" />
<span>{{ getEventDateString }}</span>
</ds-text>
<ds-text v-if="getEventTimeString" align="left" color="soft" class="event-info">
<base-icon name="clock" data-test="calendar" />
<span>{{ getEventTimeString }}</span>
</ds-text>
</ds-space>
<ds-space margin-bottom="small" />
<content-viewer class="content hyphenate-text" :content="post.content" />
<!-- Categories -->
@ -147,6 +174,7 @@ import { groupQuery } from '~/graphql/groups'
import PostMutations from '~/graphql/PostMutations'
import links from '~/constants/links.js'
import SortCategories from '~/mixins/sortCategoriesMixin.js'
import { format } from 'date-fns'
export default {
name: 'PostSlug',
@ -218,6 +246,10 @@ export default {
},
]
},
heading() {
if (this.post?.postType[0] === 'Event') return this.$t('post.viewEvent.title')
return this.$t('post.viewPost.title')
},
menuModalsData() {
return postMenuModalsData(
// "this.post" may not always be defined at the beginning
@ -256,6 +288,27 @@ export default {
!this.post.group || (this.group && ['usual', 'admin', 'owner'].includes(this.group.myRole))
)
},
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')
}
},
getEventTimeString() {
if (this.post.eventEnd) {
const eventStartTime = format(new Date(this.post.eventStart), 'HH:mm')
const eventEndTime = format(new Date(this.post.eventEnd), 'HH:mm')
/* assumption that if e.g. 00:00 == 00:00 is saved,
it's not realistic because they are the default values, so don't show the time info.
*/
return eventStartTime !== eventEndTime ? `${eventStartTime} - ${eventEndTime}` : ''
} else {
return format(new Date(this.post.eventStart), 'HH:mm')
}
},
},
methods: {
reply(message) {
@ -374,6 +427,12 @@ export default {
filter: blur($blur-radius);
}
& .event-info {
display: flex;
align-items: center;
gap: 2px;
}
.blur-toggle {
position: absolute;
bottom: 0;

View File

@ -1,15 +1,56 @@
<template>
<div>
<ds-space margin="small">
<ds-heading tag="h1">{{ $t('post.createNewPost.title') }}</ds-heading>
<ds-heading v-if="group" tag="h2">
{{ $t('post.createNewPost.forGroup.title', { name: group.name }) }}
</ds-heading>
</ds-space>
<ds-space margin="large" />
<ds-flex :width="{ base: '100%' }">
<ds-flex-item :width="{ base: '100%', md: 5 }">
<ds-flex gutter="base" :width="{ base: '100%', sm: 1 }">
<ds-flex-item>
<ds-card :primary="!creatEvent" centered>
<div>
<ds-button
v-if="!creatEvent"
ghost
fullwidth
size="x-large"
class="inactive-tab-button"
>
{{ $t('post.createNewPost.title') }}
</ds-button>
<ds-button v-else ghost fullwidth size="x-large" @click="creatEvent = !creatEvent">
{{ $t('post.createNewPost.title') }}
</ds-button>
</div>
</ds-card>
</ds-flex-item>
<ds-flex-item>
<ds-card :primary="!!creatEvent" centered>
<div>
<ds-button
ghost
fullwidth
size="x-large"
v-if="creatEvent"
hover
class="inactive-tab-button"
>
{{ $t('post.createNewEvent.title') }}
</ds-button>
<ds-button ghost fullwidth size="x-large" v-else @click="creatEvent = !creatEvent">
{{ $t('post.createNewEvent.title') }}
</ds-button>
</div>
</ds-card>
</ds-flex-item>
</ds-flex>
<div v-if="group" class="group-create-title">
{{ $t('post.createNewPost.forGroup.title', { name: group.name }) }}
</div>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>
<ds-flex :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', md: 5 }">
<contribution-form :group="group" />
<contribution-form :group="group" :creatEvent="creatEvent" />
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>
@ -28,6 +69,7 @@ export default {
const { groupId = null } = this.$route.query
return {
groupId,
creatEvent: false,
}
},
computed: {
@ -58,3 +100,13 @@ export default {
},
}
</script>
<style lang="scss">
.inactive-tab-button {
background-color: #ff000000 !important;
color: 'whitesmoke' !important;
}
.group-create-title {
font-size: 30px;
text-align: center;
}
</style>

View File

@ -63,6 +63,12 @@ export const mutations = {
if (isEmpty(get(filter, 'categories_some.id_in'))) delete filter.categories_some
state.filter = filter
},
TOGGLE_POST_TYPE(state, postType) {
const filter = clone(state.filter)
update(filter, 'postType_in', (postTypes) => xor(postTypes, [postType]))
if (isEmpty(get(filter, 'postType_in'))) delete filter.postType_in
state.filter = filter
},
TOGGLE_LANGUAGE(state, languageCode) {
const filter = clone(state.filter)
update(filter, 'language_in', (languageCodes) => xor(languageCodes, [languageCode]))
@ -90,6 +96,9 @@ export const getters = {
filteredCategoryIds(state) {
return get(state.filter, 'categories_some.id_in') || []
},
filteredPostTypes(state) {
return get(state.filter, 'postType_in') || []
},
filteredLanguageCodes(state) {
return get(state.filter, 'language_in') || []
},

View File

@ -25,6 +25,18 @@ describe('getters', () => {
})
})
describe('filteredPostTypes', () => {
it('returns post types if filter is set', () => {
state = { filter: { postType_in: ['Article', 'Event'] } }
expect(getters.filteredPostTypes(state)).toEqual(['Article', 'Event'])
})
it('returns empty array if post type filter is not set', () => {
state = { filter: { author: { followedBy_some: { id: 7 } } } }
expect(getters.filteredPostTypes(state)).toEqual([])
})
})
describe('filteredLanguageCodes', () => {
it('returns category ids if filter is set', () => {
state = { filter: { language_in: ['en', 'de', 'pt'] } }
@ -213,6 +225,46 @@ describe('mutations', () => {
})
})
describe('TOGGLE_POST_TYPE', () => {
beforeEach(() => {
testMutation = (postType) => {
mutations.TOGGLE_POST_TYPE(state, postType)
return getters.filter(state)
}
})
it('creates post type filter if empty', () => {
state = { filter: {} }
expect(testMutation('Event')).toEqual({ postType_in: ['Event'] })
})
it('adds post type not present', () => {
state = { filter: { postType_in: ['Event'] } }
expect(testMutation('Article')).toEqual({ postType_in: ['Event', 'Article'] })
})
it('removes category id if present', () => {
state = { filter: { postType_in: ['Event', 'Article'] } }
const result = testMutation('Event')
expect(result).toEqual({ postType_in: ['Article'] })
})
it('removes category filter if empty', () => {
state = { filter: { postType_in: ['Event'] } }
expect(testMutation('Event')).toEqual({})
})
it('does not get in the way of other filters', () => {
state = {
filter: {
author: { followedBy_some: { id: 7 } },
postType_in: ['Event'],
},
}
expect(testMutation('Event')).toEqual({ author: { followedBy_some: { id: 7 } } })
})
})
describe('TOGGLE_FILTER_BY_FOLLOWED', () => {
beforeEach(() => {
testMutation = (userId) => {

View File

@ -82,6 +82,7 @@ const helpers = {
commentsCount: faker.random.number(),
clickedCount: faker.random.number(),
viewedTeaserCount: faker.random.number(),
postType: ['Article'],
}
})
},

View File

@ -9776,6 +9776,11 @@ date-fns@^1.27.2:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
date-format-parse@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/date-format-parse/-/date-format-parse-0.2.7.tgz#a2f78bca857a821785b48abedd4426c65aa7b918"
integrity sha512-/+lyMUKoRogMuTeOVii6lUwjbVlesN9YRYLzZT/g3TEZ3uD9QnpjResujeEqUW+OSNbT7T1+SYdyEkTcRv+KDQ==
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@ -22091,6 +22096,13 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-datepicker@^3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/vue2-datepicker/-/vue2-datepicker-3.11.1.tgz#b2124e15f694d0fd43a92558f6929ec29338d241"
integrity sha512-6PU/+pnp2mgZAfnSXmbdwj9516XsEvTiw61Q5SNrvvdy8W/FCxk1GAe9UZn/m9YfS5A47yK6XkcjMHbp7aFApA==
dependencies:
date-format-parse "^0.2.7"
vue2-dropzone@3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.6.0.tgz#b4bb4b64de1cbbb3b88f04b24878e06780a51546"

421
yarn.lock
View File

@ -1253,6 +1253,11 @@
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.1.0.tgz#8f7627b23ed9bf67088fc7f9669e48c63ad421bd"
integrity sha512-b1J4jxYnW+n6lC91V6Pqg9imP9BZq0HNCeM+3sbXg05rQsE9cGYrKFpZjyztVesGmNRE6R+QaEoWGATeIiUVjA==
"@hapi/hoek@^9.0.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
"@hapi/joi@^15.1.1":
version "15.1.1"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7"
@ -1270,6 +1275,13 @@
dependencies:
"@hapi/hoek" "8.x.x"
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@jest/expect-utils@^29.5.0":
version "29.5.0"
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036"
@ -1343,6 +1355,23 @@
dependencies:
any-observable "^0.3.0"
"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinclair/typebox@^0.25.16":
version "0.25.24"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
@ -1447,13 +1476,6 @@ acorn@^6.0.2:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
agent-base@6:
version "6.0.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==
dependencies:
debug "4"
ajv@^6.5.5:
version "6.10.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
@ -1552,11 +1574,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
argv@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@ -1694,6 +1711,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
babel-plugin-add-module-exports@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.0.tgz#72b5424d941a336c6a35357f373d8b8366263031"
@ -2213,17 +2238,6 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codecov@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.2.tgz#ab24f18783998c39e809ea210af899f8dbcc790e"
integrity sha512-6w/kt/xvmPsWMfDFPE/T054txA9RTgcJEw36PNa6MYX+YV29jCHCRFXwbQ3QZBTOgnex1J2WP8bo2AT8TWWz9g==
dependencies:
argv "0.0.2"
ignore-walk "3.0.3"
js-yaml "3.14.1"
teeny-request "7.0.1"
urlgrey "0.4.4"
coffeeify@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/coffeeify/-/coffeeify-3.0.1.tgz#5e2753000c50bd24c693115f33864248dd11136c"
@ -2296,6 +2310,13 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^2.9.0, commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@ -2445,17 +2466,6 @@ cross-env@^7.0.3:
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -2700,13 +2710,6 @@ dayjs@^1.10.4:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@ -2735,6 +2738,13 @@ debug@^3.0.1, debug@^3.1.0:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.1.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@ -2752,13 +2762,6 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
define-properties@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
dependencies:
object-keys "^1.0.12"
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@ -2946,27 +2949,6 @@ error-stack-parser@^2.0.1:
dependencies:
stackframe "^1.0.4"
es-abstract@^1.4.3:
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==
dependencies:
es-to-primitive "^1.2.0"
function-bind "^1.1.1"
has "^1.0.3"
is-callable "^1.1.4"
is-regex "^1.0.4"
object-keys "^1.0.12"
es-to-primitive@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.46:
version "0.10.49"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.49.tgz#059a239de862c94494fec28f8150c977028c6c5e"
@ -3213,6 +3195,11 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
follow-redirects@^1.14.9:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -3223,6 +3210,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@ -3278,7 +3274,7 @@ fsevents@^1.2.7:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
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==
@ -3441,11 +3437,6 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@ -3482,7 +3473,7 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
has@^1.0.0, has@^1.0.1, has@^1.0.3:
has@^1.0.0, has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
@ -3514,24 +3505,11 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4:
version "2.7.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==
htmlescape@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=
http-proxy-agent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.0.tgz#6b74d332e1934a1107b97e97de4a00e267c790fe"
integrity sha512-GX0FA6+IcDf4Oxc/FBWgYj4zKgo/DnZrksaG9jyuQLExs6xlX+uI5lcA8ymM3JaZTRrF/4s2UX19wJolyo7OBA==
dependencies:
agent-base "6"
debug "4"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -3546,14 +3524,6 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@ -3571,7 +3541,7 @@ ieee754@^1.1.4:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
ignore-walk@3.0.3, ignore-walk@^3.0.1:
ignore-walk@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
@ -3689,11 +3659,6 @@ is-buffer@^1.1.0, is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
is-ci@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994"
@ -3722,11 +3687,6 @@ is-data-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-date-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@ -3847,13 +3807,6 @@ is-promise@^2.1.0:
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
is-regex@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=
dependencies:
has "^1.0.1"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@ -3864,13 +3817,6 @@ is-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
dependencies:
has-symbols "^1.0.0"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@ -3970,6 +3916,17 @@ jest-util@^29.5.0:
graceful-fs "^4.2.9"
picomatch "^2.2.3"
joi@^17.7.0:
version "17.9.2"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690"
integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.3"
"@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0"
js-levenshtein@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
@ -3985,7 +3942,7 @@ js-string-escape@^1.0.1:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@3.14.1, js-yaml@^3.9.0:
js-yaml@^3.9.0:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
@ -4198,16 +4155,6 @@ listr@^0.14.3:
p-map "^2.0.0"
rxjs "^6.3.3"
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@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@ -4343,11 +4290,6 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
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-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@ -4437,6 +4379,11 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
@ -4600,11 +4547,6 @@ next-tick@^1.0.0:
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
@ -4617,11 +4559,6 @@ node-fetch@^2.6.0:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-modules-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
@ -4656,16 +4593,6 @@ nopt@^4.0.1:
abbrev "1"
osenv "^0.1.4"
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@ -4691,21 +4618,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@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@ -4747,11 +4659,6 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-keys@^1.0.12:
version "1.1.0"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032"
integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@ -4935,11 +4842,6 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
path-key@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
@ -4960,13 +4862,6 @@ path-platform@~0.11.15:
resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2"
integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=
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"
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
@ -5003,21 +4898,11 @@ 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.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@ -5173,15 +5058,6 @@ read-only-stream@^2.0.0:
dependencies:
readable-stream "^2.0.2"
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.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
@ -5312,7 +5188,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.1.4, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.8.1:
resolve@^1.1.4, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.8.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@ -5376,6 +5252,13 @@ rxjs@^6.3.3, rxjs@^6.6.3:
dependencies:
tslib "^1.9.0"
rxjs@^7.8.0:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^2.1.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -5403,7 +5286,7 @@ seed-random@~2.2.0:
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"
integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -5456,13 +5339,6 @@ shasum@^1.0.0:
json-stable-stringify "~0.0.0"
sha.js "~2.4.4"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
dependencies:
shebang-regex "^1.0.0"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -5470,11 +5346,6 @@ shebang-command@^2.0.0:
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
@ -5584,32 +5455,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spdx-correct@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
spdx-expression-parse@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e"
integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@ -5702,13 +5547,6 @@ stream-combiner2@^1.1.1:
duplexer2 "~0.1.0"
readable-stream "^2.0.2"
stream-events@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
dependencies:
stubs "^3.0.0"
stream-http@^2.0.0:
version "2.8.3"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
@ -5764,15 +5602,6 @@ string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.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_decoder@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
@ -5808,11 +5637,6 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
strip-final-newline@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
@ -5823,11 +5647,6 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
stubs@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls=
subarg@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
@ -5891,17 +5710,6 @@ tar@^4:
safe-buffer "^5.1.2"
yallist "^3.0.2"
teeny-request@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c"
integrity sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==
dependencies:
http-proxy-agent "^4.0.0"
https-proxy-agent "^5.0.0"
node-fetch "^2.6.1"
stream-events "^1.0.5"
uuid "^8.0.0"
text-encoding-utf-8@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13"
@ -6016,6 +5824,11 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
tslib@^2.1.0:
version "2.5.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
tty-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811"
@ -6174,11 +5987,6 @@ url@^0.11.0, url@~0.11.0:
punycode "1.3.2"
querystring "0.2.0"
urlgrey@0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@ -6218,19 +6026,6 @@ uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.0.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
verror@1.10.0, verror@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
@ -6245,6 +6040,17 @@ vm-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
wait-on@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9"
integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==
dependencies:
axios "^0.27.2"
joi "^17.7.0"
lodash "^4.17.21"
minimist "^1.2.7"
rxjs "^7.8.0"
watchify@3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/watchify/-/watchify-3.11.1.tgz#8e4665871fff1ef64c0430d1a2c9d084d9721881"
@ -6258,13 +6064,6 @@ watchify@3.11.1:
through2 "^2.0.0"
xtend "^4.0.0"
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"