mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' into dependabot/docker/backend/neo4j/neo4j-3.5.3
This commit is contained in:
commit
63a646c6b1
11
.github/ISSUE_TEMPLATE.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<!--
|
||||
Please take a look at the issue templates at https://github.com/Human-Connection/Human-Connection/issues/new/choose
|
||||
before submitting a new issue. Following one of the issue templates will ensure maintainers can route your request efficiently.
|
||||
|
||||
Thanks!
|
||||
-->
|
||||
|
||||
## Issue
|
||||
<!-- Describe your Issue in detail. -->
|
||||
|
||||
<!-- Attach screenshots and drawings if needed. -->
|
||||
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,35 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
name: 🐛 Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
## :bug: Bugreport
|
||||
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.-->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to 'http...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
### Steps to reproduce the behavior
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4. ...
|
||||
5. Profit
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
### Expected behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
### Version & Environment
|
||||
Type: [] <!-- [Desktop|Smartphone] -->
|
||||
- OS: [] <!-- [e.g. iOS8.1 or Windows] -->
|
||||
- Browser: [] <!-- [e.g. stock browser, safari, chrome] -->
|
||||
- Version [] <!-- [e.g. 22] -->
|
||||
- Device: [] <!-- [e.g. iPhone6] -->
|
||||
|
||||
### Additional context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,17 +1,26 @@
|
||||
---
|
||||
name: Feature request
|
||||
name: 🚀 Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
## :rocket: Feature
|
||||
<!-- Describe the Feature. -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
### Is your feature request related to a problem? Please describe.
|
||||
<!-- A clear and concise description of what the problem is.
|
||||
Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
### Describe the prefered solution and alternatives you've considered
|
||||
<!-- A clear and concise description of what you want to happen.
|
||||
Are there any alternative solutions or features you've considered? -->
|
||||
|
||||
|
||||
### Design & Layout
|
||||
<!-- Attach Screenshots and Drawings. -->
|
||||
|
||||
|
||||
### Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here.-->
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: 💬 Question
|
||||
about: If you need help understanding HumanConnection.
|
||||
---
|
||||
<!-- Chat with Team HumanConnection -->
|
||||
<!-- If you need an answer right away, visit the HumanConnection Discord:
|
||||
https://discord.gg/Q3mpcgr -->
|
||||
|
||||
## :speech_balloon: Question
|
||||
<!-- Describe your Question in detail. Include screenshots and drawings if needed. -->
|
||||
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
28
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
## Pullrequest
|
||||
<!-- Describe the Pullrequest. -->
|
||||
|
||||
### Issues
|
||||
<!-- Which Issues does this fix, which are related?
|
||||
- fixes #XXX
|
||||
- relates #XXX
|
||||
-->
|
||||
- [X] None
|
||||
|
||||
### Checklist
|
||||
<!-- Anything important to be thought of when deploying?
|
||||
- [ ] Env-Variables adjustment needed
|
||||
- [ ] Breaking/critical change
|
||||
-->
|
||||
- [X] None
|
||||
|
||||
### How2Test
|
||||
<!-- Give a detailed description how to test your PR and confirm it is working as expected. -->
|
||||
<!-- Maintainers will check the Tests
|
||||
- [ ] Test1
|
||||
- [ ] Test2
|
||||
-->
|
||||
- [X] None
|
||||
|
||||
### Todo
|
||||
<!-- In case some parts are still missing, list them here. -->
|
||||
- [X] None
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.env
|
||||
.idea
|
||||
*.iml
|
||||
.vscode
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
@ -7,8 +8,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.yarn-integrity
|
||||
.eslintcache
|
||||
|
||||
/.github
|
||||
kubeconfig.yaml
|
||||
|
||||
node_modules/
|
||||
cypress/videos
|
||||
|
||||
25
.travis.yml
25
.travis.yml
@ -1,13 +1,12 @@
|
||||
dist: xenial
|
||||
language: generic
|
||||
services:
|
||||
- docker
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
- libgconf-2-4
|
||||
snaps:
|
||||
- docker
|
||||
- chromium
|
||||
|
||||
before_install:
|
||||
- yarn global add wait-on
|
||||
@ -20,16 +19,15 @@ install:
|
||||
|
||||
script:
|
||||
- docker-compose exec backend yarn run lint
|
||||
- docker-compose exec backend yarn run test --ci
|
||||
- docker-compose exec backend yarn run test:jest --ci --verbose=false
|
||||
- docker-compose exec backend yarn run db:reset
|
||||
- docker-compose exec backend yarn run db:seed
|
||||
- docker-compose exec backend yarn run test:cucumber
|
||||
- docker-compose exec backend yarn run test:coverage
|
||||
- docker-compose exec backend yarn run db:reset
|
||||
- docker-compose exec backend yarn run db:seed
|
||||
- docker-compose exec webapp yarn run lint
|
||||
- docker-compose exec webapp yarn run test --ci
|
||||
- docker-compose exec -d backend yarn run test:cypress
|
||||
- docker-compose exec webapp yarn run test --ci --verbose=false
|
||||
- docker-compose exec -d backend yarn run test:before:seeder
|
||||
- yarn run cypress:run --record --key $CYPRESS_TOKEN
|
||||
|
||||
after_success:
|
||||
@ -47,8 +45,15 @@ after_failure:
|
||||
- chmod +x send.sh
|
||||
- ./send.sh failure $WEBHOOK_URL
|
||||
|
||||
before_deploy:
|
||||
- ./scripts/setup_kubernetes.sh
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
script: scripts/docker_push.sh
|
||||
on:
|
||||
branch: master
|
||||
- provider: script
|
||||
script: scripts/deploy.sh
|
||||
on:
|
||||
branch: master
|
||||
|
||||
@ -34,6 +34,13 @@ Connect with other developers over [Discord](https://discord.gg/6ub73U3)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
Node >= `v10.12.0`
|
||||
```
|
||||
node --version
|
||||
```
|
||||
|
||||
### Forking the repository
|
||||
Before you start, fork the repository using the fork button above, then clone it to your local machine using `git clone https://github.com/your-username/Nitro-Backend.git`
|
||||
|
||||
@ -63,6 +70,17 @@ docker-compose down -v
|
||||
|
||||
Install dependencies:
|
||||
|
||||
Download [Neo4j Community Edition](https://neo4j.com/download-center/#releases) and unpack the files.
|
||||
|
||||
Download [Neo4j Apoc](https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases) and drop the file into the `plugins` folder of the just extracted Neo4j-Server
|
||||
|
||||
Start Neo4j
|
||||
```
|
||||
neo4j\bin\neo4j start
|
||||
```
|
||||
and confirm it's running [here](http://localhost:7474)
|
||||
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
# -or-
|
||||
|
||||
@ -3,29 +3,25 @@
|
||||
"version": "0.0.1",
|
||||
"description": "GraphQL Backend for Human Connection",
|
||||
"main": "src/index.js",
|
||||
"config": {
|
||||
"no_auth": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 PERMISSIONS=disabled"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "babel src/ -d dist/ --copy-files",
|
||||
"start": "node dist/",
|
||||
"dev": "nodemon --exec babel-node src/ -e js,graphql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql",
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"test": "nyc --reporter=text-lcov yarn test:jest",
|
||||
"test:cypress": "run-p --race test:before:*",
|
||||
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null",
|
||||
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 PERMISSIONS=disabled babel-node src/ 2> /dev/null",
|
||||
"test": "run-s test:jest test:cucumber",
|
||||
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev 2> /dev/null",
|
||||
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev",
|
||||
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
|
||||
"test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/",
|
||||
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
|
||||
"test:jest": "run-p --race test:before:* 'test:jest:cmd {@}' --",
|
||||
"test:cucumber": "run-p --race test:before:* 'test:cucumber:cmd {@}' --",
|
||||
"test:cucumber": " cross-env CLIENT_URI=http://localhost:4123 run-p --race test:before:server test:cucumber:before:seeder 'test:cucumber:cmd {@}' --",
|
||||
"test:cucumber:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions yarn run dev",
|
||||
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
|
||||
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
|
||||
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
|
||||
"db:reset": "babel-node src/seed/reset-db.js",
|
||||
"db:seed": "$npm_package_config_no_auth run-p --race dev db:script:seed"
|
||||
"db:seed": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions run-p --race dev db:script:seed"
|
||||
},
|
||||
"author": "Human Connection gGmbH",
|
||||
"license": "MIT",
|
||||
@ -40,7 +36,7 @@
|
||||
"apollo-cache-inmemory": "~1.5.1",
|
||||
"apollo-client": "~2.5.1",
|
||||
"apollo-link-context": "~1.0.14",
|
||||
"apollo-link-http": "~1.5.13",
|
||||
"apollo-link-http": "~1.5.14",
|
||||
"apollo-server": "~2.4.8",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.2",
|
||||
@ -51,14 +47,14 @@
|
||||
"dotenv": "~7.0.0",
|
||||
"express": "~4.16.4",
|
||||
"faker": "~4.1.0",
|
||||
"graphql": "~14.1.1",
|
||||
"graphql": "~14.2.1",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.3.0",
|
||||
"graphql-shield": "~5.3.1",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"helmet": "~3.15.1",
|
||||
"helmet": "~3.16.0",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.11",
|
||||
@ -69,35 +65,34 @@
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.0",
|
||||
"sanitize-html": "~1.20.0",
|
||||
"slug": "~1.0.0",
|
||||
"slug": "~1.1.0",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~3.3.2",
|
||||
"wait-on": "~3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.2.3",
|
||||
"@babel/core": "~7.3.4",
|
||||
"@babel/core": "~7.4.3",
|
||||
"@babel/node": "~7.2.2",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/preset-env": "~7.3.4",
|
||||
"@babel/register": "~7.0.0",
|
||||
"@babel/preset-env": "~7.4.3",
|
||||
"@babel/register": "~7.4.0",
|
||||
"apollo-server-testing": "~2.4.8",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-jest": "~24.5.0",
|
||||
"babel-jest": "~24.7.1",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~5.15.1",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.16.0",
|
||||
"eslint-plugin-jest": "~22.3.2",
|
||||
"eslint-plugin-jest": "~22.4.1",
|
||||
"eslint-plugin-node": "~8.0.1",
|
||||
"eslint-plugin-promise": "~4.0.1",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
"eslint-plugin-standard": "~4.0.0",
|
||||
"graphql-request": "~1.8.2",
|
||||
"jest": "~24.5.0",
|
||||
"jest": "~24.7.1",
|
||||
"nodemon": "~1.18.10",
|
||||
"nyc": "~13.3.0",
|
||||
"supertest": "~4.0.0"
|
||||
"supertest": "~4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,22 +22,19 @@ let activityPub = null
|
||||
export { activityPub }
|
||||
|
||||
export default class ActivityPub {
|
||||
constructor (domain, port, uri) {
|
||||
if (domain === 'localhost') { this.domain = `${domain}:${port}` } else { this.domain = domain }
|
||||
this.port = port
|
||||
this.dataSource = new NitroDataSource(uri)
|
||||
constructor (activityPubEndpointUri, internalGraphQlUri) {
|
||||
this.endpoint = activityPubEndpointUri
|
||||
this.dataSource = new NitroDataSource(internalGraphQlUri)
|
||||
this.collections = new Collections(this.dataSource)
|
||||
}
|
||||
|
||||
static init (server) {
|
||||
if (!activityPub) {
|
||||
dotenv.config()
|
||||
const url = new URL(process.env.GRAPHQL_URI)
|
||||
activityPub = new ActivityPub(url.hostname || 'localhost', url.port || 4000, url.origin)
|
||||
activityPub = new ActivityPub(process.env.CLIENT_URI || 'http://localhost:3000', process.env.GRAPHQL_URI || 'http://localhost:4000')
|
||||
|
||||
// integrate into running graphql express server
|
||||
server.express.set('ap', activityPub)
|
||||
server.express.set('port', url.port)
|
||||
server.express.use(router)
|
||||
console.log('-> ActivityPub middleware added to the graphql express server')
|
||||
} else {
|
||||
@ -59,7 +56,6 @@ export default class ActivityPub {
|
||||
}
|
||||
}, async (err, response, toActorObject) => {
|
||||
if (err) return reject(err)
|
||||
debug(`name = ${toActorName}@${this.domain}`)
|
||||
// save shared inbox
|
||||
toActorObject = JSON.parse(toActorObject)
|
||||
await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox)
|
||||
@ -184,7 +180,7 @@ export default class ActivityPub {
|
||||
}
|
||||
|
||||
generateStatusId (slug) {
|
||||
return `http://${this.domain}/activitypub/users/${slug}/status/${uuid()}`
|
||||
return `https://${this.host}/activitypub/users/${slug}/status/${uuid()}`
|
||||
}
|
||||
|
||||
async sendActivity (activity) {
|
||||
|
||||
@ -12,15 +12,20 @@ router.get('/', async function (req, res) {
|
||||
const nameAndDomain = resource.replace('acct:', '')
|
||||
const name = nameAndDomain.split('@')[0]
|
||||
|
||||
const result = await req.app.get('ap').dataSource.client.query({
|
||||
query: gql`
|
||||
let result
|
||||
try {
|
||||
result = await req.app.get('ap').dataSource.client.query({
|
||||
query: gql`
|
||||
query {
|
||||
User(slug: "${name}") {
|
||||
slug
|
||||
}
|
||||
}
|
||||
`
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error })
|
||||
}
|
||||
|
||||
if (result.data && result.data.User.length > 0) {
|
||||
const webFinger = createWebFinger(name)
|
||||
|
||||
@ -11,14 +11,14 @@ export function createNoteObject (text, name, id, published) {
|
||||
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${createUuid}`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${createUuid}`,
|
||||
'type': 'Create',
|
||||
'actor': `https://${activityPub.domain}/activitypub/users/${name}`,
|
||||
'actor': `${activityPub.endpoint}/activitypub/users/${name}`,
|
||||
'object': {
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}/status/${id}`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${id}`,
|
||||
'type': 'Note',
|
||||
'published': published,
|
||||
'attributedTo': `https://${activityPub.domain}/activitypub/users/${name}`,
|
||||
'attributedTo': `${activityPub.endpoint}/activitypub/users/${name}`,
|
||||
'content': text,
|
||||
'to': 'https://www.w3.org/ns/activitystreams#Public'
|
||||
}
|
||||
@ -64,8 +64,8 @@ export async function getActorId (name) {
|
||||
|
||||
export function sendAcceptActivity (theBody, name, targetDomain, url) {
|
||||
as.accept()
|
||||
.id(`https://${activityPub.domain}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
|
||||
.actor(`https://${activityPub.domain}/activitypub/users/${name}`)
|
||||
.id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
|
||||
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
.object(theBody)
|
||||
.prettyWrite((err, doc) => {
|
||||
if (!err) {
|
||||
@ -79,8 +79,8 @@ export function sendAcceptActivity (theBody, name, targetDomain, url) {
|
||||
|
||||
export function sendRejectActivity (theBody, name, targetDomain, url) {
|
||||
as.reject()
|
||||
.id(`https://${activityPub.domain}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
|
||||
.actor(`https://${activityPub.domain}/activitypub/users/${name}`)
|
||||
.id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
|
||||
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
|
||||
.object(theBody)
|
||||
.prettyWrite((err, doc) => {
|
||||
if (!err) {
|
||||
|
||||
@ -6,34 +6,35 @@ export function createActor (name, pubkey) {
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1'
|
||||
],
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}`,
|
||||
'type': 'Person',
|
||||
'preferredUsername': `${name}`,
|
||||
'name': `${name}`,
|
||||
'following': `https://${activityPub.domain}/activitypub/users/${name}/following`,
|
||||
'followers': `https://${activityPub.domain}/activitypub/users/${name}/followers`,
|
||||
'inbox': `https://${activityPub.domain}/activitypub/users/${name}/inbox`,
|
||||
'outbox': `https://${activityPub.domain}/activitypub/users/${name}/outbox`,
|
||||
'url': `https://${activityPub.domain}/activitypub/@${name}`,
|
||||
'following': `${activityPub.endpoint}/activitypub/users/${name}/following`,
|
||||
'followers': `${activityPub.endpoint}/activitypub/users/${name}/followers`,
|
||||
'inbox': `${activityPub.endpoint}/activitypub/users/${name}/inbox`,
|
||||
'outbox': `${activityPub.endpoint}/activitypub/users/${name}/outbox`,
|
||||
'url': `${activityPub.endpoint}/activitypub/@${name}`,
|
||||
'endpoints': {
|
||||
'sharedInbox': `https://${activityPub.domain}/activitypub/inbox`
|
||||
'sharedInbox': `${activityPub.endpoint}/activitypub/inbox`
|
||||
},
|
||||
'publicKey': {
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}#main-key`,
|
||||
'owner': `https://${activityPub.domain}/activitypub/users/${name}`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}#main-key`,
|
||||
'owner': `${activityPub.endpoint}/activitypub/users/${name}`,
|
||||
'publicKeyPem': pubkey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createWebFinger (name) {
|
||||
const { host } = new URL(activityPub.endpoint)
|
||||
return {
|
||||
'subject': `acct:${name}@${activityPub.domain}`,
|
||||
'subject': `acct:${name}@${host}`,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': `https://${activityPub.domain}/users/${name}`
|
||||
'href': `${activityPub.endpoint}/activitypub/users/${name}`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@ const debug = require('debug')('ea:utils:collections')
|
||||
export function createOrderedCollection (name, collectionName) {
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`,
|
||||
'summary': `${name}s ${collectionName} collection`,
|
||||
'type': 'OrderedCollection',
|
||||
'first': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}?page=true`,
|
||||
'first': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`,
|
||||
'totalItems': 0
|
||||
}
|
||||
}
|
||||
@ -16,11 +16,11 @@ export function createOrderedCollection (name, collectionName) {
|
||||
export function createOrderedCollectionPage (name, collectionName) {
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}?page=true`,
|
||||
'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`,
|
||||
'summary': `${name}s ${collectionName} collection`,
|
||||
'type': 'OrderedCollectionPage',
|
||||
'totalItems': 0,
|
||||
'partOf': `https://${activityPub.domain}/activitypub/users/${name}/${collectionName}`,
|
||||
'partOf': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`,
|
||||
'orderedItems': []
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,13 +19,12 @@ export function extractIdFromActivityId (uri) {
|
||||
|
||||
return splitted[splitted.indexOf('status') + 1]
|
||||
}
|
||||
|
||||
export function constructIdFromName (name, fromDomain = activityPub.domain) {
|
||||
return `http://${fromDomain}/activitypub/users/${name}`
|
||||
export function constructIdFromName (name, fromDomain = activityPub.endpoint) {
|
||||
return `${fromDomain}/activitypub/users/${name}`
|
||||
}
|
||||
|
||||
export function extractDomainFromUrl (url) {
|
||||
return new URL(url).hostname
|
||||
return new URL(url).host
|
||||
}
|
||||
|
||||
export function throwErrorIfApolloErrorOccurred (result) {
|
||||
@ -76,7 +75,7 @@ export function signAndSend (activity, fromName, targetDomain, url) {
|
||||
'Host': targetDomain,
|
||||
'Date': date,
|
||||
'Signature': createSignature({ privateKey,
|
||||
keyId: `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`,
|
||||
keyId: `${activityPub.endpoint}/activitypub/users/${fromName}#main-key`,
|
||||
url,
|
||||
headers: {
|
||||
'Host': targetDomain,
|
||||
|
||||
@ -6,6 +6,7 @@ import statistics from './resolvers/statistics.js'
|
||||
import reports from './resolvers/reports.js'
|
||||
import posts from './resolvers/posts.js'
|
||||
import moderation from './resolvers/moderation.js'
|
||||
import rewards from './resolvers/rewards.js'
|
||||
|
||||
export const typeDefs = fs
|
||||
.readFileSync(
|
||||
@ -21,7 +22,8 @@ export const resolvers = {
|
||||
Mutation: {
|
||||
...userManagement.Mutation,
|
||||
...reports.Mutation,
|
||||
...posts.Mutation,
|
||||
...moderation.Mutation,
|
||||
...posts.Mutation
|
||||
...rewards.Mutation
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,6 @@ const serverConfig = {
|
||||
const server = createServer()
|
||||
server.start(serverConfig, options => {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(`Server ready at ${process.env.GRAPHQL_URI} 🚀`)
|
||||
console.log(`GraphQLServer ready at ${process.env.GRAPHQL_URI} 🚀`)
|
||||
ActivityPub.init(server)
|
||||
})
|
||||
|
||||
@ -49,7 +49,7 @@ export default {
|
||||
CreateUser: async (resolve, root, args, context, info) => {
|
||||
const keys = generateRsaKeyPair()
|
||||
Object.assign(args, keys)
|
||||
args.actorId = `${process.env.GRAPHQL_URI}/activitypub/users/${args.slug}`
|
||||
args.actorId = `${activityPub.host}/activitypub/users/${args.slug}`
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +1,23 @@
|
||||
const setCreatedAt = (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
const setUpdatedAt = (resolve, root, args, context, info) => {
|
||||
args.updatedAt = (new Date()).toISOString()
|
||||
return resolve(root, args, context, info)
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateUser: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreateComment: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreateOrganization: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
UpdateUser: async (resolve, root, args, context, info) => {
|
||||
args.updatedAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
UpdatePost: async (resolve, root, args, context, info) => {
|
||||
args.updatedAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
UpdateComment: async (resolve, root, args, context, info) => {
|
||||
args.updatedAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
UpdateOrganization: async (resolve, root, args, context, info) => {
|
||||
args.updatedAt = (new Date()).toISOString()
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
}
|
||||
CreateUser: setCreatedAt,
|
||||
CreatePost: setCreatedAt,
|
||||
CreateComment: setCreatedAt,
|
||||
CreateOrganization: setCreatedAt,
|
||||
CreateNotification: setCreatedAt,
|
||||
UpdateUser: setUpdatedAt,
|
||||
UpdatePost: setUpdatedAt,
|
||||
UpdateComment: setUpdatedAt,
|
||||
UpdateOrganization: setUpdatedAt,
|
||||
UpdateNotification: setUpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,6 @@ const includeFieldsRecursively = (includedFields) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: includeFieldsRecursively(['id', 'disabled', 'deleted']),
|
||||
Mutation: includeFieldsRecursively(['id', 'disabled', 'deleted'])
|
||||
Query: includeFieldsRecursively(['id', 'createdAt', 'disabled', 'deleted']),
|
||||
Mutation: includeFieldsRecursively(['id', 'createdAt', 'disabled', 'deleted'])
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import xssMiddleware from './xssMiddleware'
|
||||
import permissionsMiddleware from './permissionsMiddleware'
|
||||
import userMiddleware from './userMiddleware'
|
||||
import includedFieldsMiddleware from './includedFieldsMiddleware'
|
||||
import orderByMiddleware from './orderByMiddleware'
|
||||
|
||||
export default schema => {
|
||||
let middleware = [
|
||||
@ -20,14 +21,17 @@ export default schema => {
|
||||
fixImageUrlsMiddleware,
|
||||
softDeleteMiddleware,
|
||||
userMiddleware,
|
||||
includedFieldsMiddleware
|
||||
includedFieldsMiddleware,
|
||||
orderByMiddleware
|
||||
]
|
||||
|
||||
// add permisions middleware at the first position (unless we're seeding)
|
||||
// NOTE: DO NOT SET THE PERMISSION FLAT YOUR SELF
|
||||
if (process.env.PERMISSIONS !== 'disabled' && process.env.NODE_ENV !== 'production') {
|
||||
middleware.unshift(activityPubMiddleware)
|
||||
middleware.unshift(permissionsMiddleware.generate(schema))
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const DISABLED_MIDDLEWARES = process.env.DISABLED_MIDDLEWARES || ''
|
||||
const disabled = DISABLED_MIDDLEWARES.split(',')
|
||||
if (!disabled.includes('activityPub')) middleware.unshift(activityPubMiddleware)
|
||||
if (!disabled.includes('permissions')) middleware.unshift(permissionsMiddleware.generate(schema))
|
||||
}
|
||||
return middleware
|
||||
}
|
||||
|
||||
19
backend/src/middleware/orderByMiddleware.js
Normal file
19
backend/src/middleware/orderByMiddleware.js
Normal file
@ -0,0 +1,19 @@
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
const defaultOrderBy = (resolve, root, args, context, resolveInfo) => {
|
||||
const copy = cloneDeep(resolveInfo)
|
||||
const newestFirst = {
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'orderBy' },
|
||||
value: { kind: 'EnumValue', value: 'createdAt_desc' }
|
||||
}
|
||||
const [fieldNode] = copy.fieldNodes
|
||||
if (fieldNode) fieldNode.arguments.push(newestFirst)
|
||||
return resolve(root, args, context, copy)
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: defaultOrderBy
|
||||
}
|
||||
}
|
||||
62
backend/src/middleware/orderByMiddleware.spec.js
Normal file
62
backend/src/middleware/orderByMiddleware.spec.js
Normal file
@ -0,0 +1,62 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { host } from '../jest/helpers'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
|
||||
let client
|
||||
let headers
|
||||
let query
|
||||
const factory = Factory()
|
||||
|
||||
beforeEach(async () => {
|
||||
const userParams = { name: 'Author', email: 'author@example.org', password: '1234' }
|
||||
await factory.create('User', userParams)
|
||||
await factory.authenticateAs(userParams)
|
||||
await factory.create('Post', { title: 'first' })
|
||||
await factory.create('Post', { title: 'second' })
|
||||
await factory.create('Post', { title: 'third' })
|
||||
await factory.create('Post', { title: 'last' })
|
||||
headers = {}
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('Query', () => {
|
||||
describe('Post', () => {
|
||||
beforeEach(() => {
|
||||
query = '{ Post { title } }'
|
||||
})
|
||||
|
||||
describe('orderBy', () => {
|
||||
it('createdAt descending is default', async () => {
|
||||
const posts = [
|
||||
{ title: 'last' },
|
||||
{ title: 'third' },
|
||||
{ title: 'second' },
|
||||
{ title: 'first' }
|
||||
]
|
||||
const expected = { Post: posts }
|
||||
await expect(client.request(query)).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
describe('(orderBy: createdAt_asc)', () => {
|
||||
beforeEach(() => {
|
||||
query = '{ Post(orderBy: createdAt_asc) { title } }'
|
||||
})
|
||||
|
||||
it('orders by createdAt ascending', async () => {
|
||||
const posts = [
|
||||
{ title: 'first' },
|
||||
{ title: 'second' },
|
||||
{ title: 'third' },
|
||||
{ title: 'last' }
|
||||
]
|
||||
const expected = { Post: posts }
|
||||
await expect(client.request(query)).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -44,6 +44,7 @@ const isAuthor = rule({ cache: 'no_cache' })(async (parent, args, { user, driver
|
||||
// Permissions
|
||||
const permissions = shield({
|
||||
Query: {
|
||||
Notification: isAdmin,
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator)
|
||||
@ -56,6 +57,11 @@ const permissions = shield({
|
||||
CreateBadge: isAdmin,
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
AddUserBadges: isAdmin,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
unreward: isAdmin,
|
||||
// addFruitToBasket: isAuthenticated
|
||||
follow: isAuthenticated,
|
||||
unfollow: isAuthenticated,
|
||||
|
||||
120
backend/src/resolvers/notifications.spec.js
Normal file
120
backend/src/resolvers/notifications.spec.js
Normal file
@ -0,0 +1,120 @@
|
||||
|
||||
import Factory from '../seed/factories'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login } from '../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
id: 'you',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('Notification', () => {
|
||||
const query = `{
|
||||
Notification {
|
||||
id
|
||||
}
|
||||
}`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(query)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('currentUser { notifications }', () => {
|
||||
let variables = {}
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe('given some notifications', () => {
|
||||
beforeEach(async () => {
|
||||
const neighborParams = {
|
||||
email: 'neighbor@example.org',
|
||||
password: '1234',
|
||||
id: 'neighbor'
|
||||
}
|
||||
await Promise.all([
|
||||
factory.create('User', neighborParams),
|
||||
factory.create('Notification', { id: 'not-for-you' }),
|
||||
factory.create('Notification', { id: 'already-seen', read: true })
|
||||
])
|
||||
await factory.create('Notification', { id: 'unseen' })
|
||||
await factory.authenticateAs(neighborParams)
|
||||
await factory.create('Post', { id: 'p1' })
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', { from: 'not-for-you', to: 'neighbor' }),
|
||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'not-for-you' }),
|
||||
factory.relate('Notification', 'User', { from: 'unseen', to: 'you' }),
|
||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'unseen' }),
|
||||
factory.relate('Notification', 'User', { from: 'already-seen', to: 'you' }),
|
||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen' })
|
||||
])
|
||||
})
|
||||
|
||||
describe('filter for read: false', () => {
|
||||
const query = `query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
id
|
||||
post {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
let variables = { read: false }
|
||||
it('returns only unread notifications of current user', async () => {
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{ id: 'unseen', post: { id: 'p1' } }
|
||||
]
|
||||
}
|
||||
}
|
||||
await expect(client.request(query, variables)).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('no filters', () => {
|
||||
const query = `{
|
||||
currentUser {
|
||||
notifications(orderBy: createdAt_desc) {
|
||||
id
|
||||
post {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
it('returns all notifications of current user', async () => {
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{ id: 'unseen', post: { id: 'p1' } },
|
||||
{ id: 'already-seen', post: { id: 'p1' } }
|
||||
]
|
||||
}
|
||||
}
|
||||
await expect(client.request(query, variables)).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
47
backend/src/resolvers/rewards.js
Normal file
47
backend/src/resolvers/rewards.js
Normal file
@ -0,0 +1,47 @@
|
||||
export default {
|
||||
Mutation: {
|
||||
reward: async (_object, params, context, _resolveInfo) => {
|
||||
const { fromBadgeId, toUserId } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let sessionRes = await session.run(
|
||||
`MATCH (badge:Badge {id: $badgeId}), (rewardedUser:User {id: $rewardedUserId})
|
||||
MERGE (badge)-[:REWARDED]->(rewardedUser)
|
||||
RETURN rewardedUser {.id}`,
|
||||
{
|
||||
badgeId: fromBadgeId,
|
||||
rewardedUserId: toUserId
|
||||
}
|
||||
)
|
||||
|
||||
const [rewardedUser] = sessionRes.records.map(record => {
|
||||
return record.get('rewardedUser')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return rewardedUser.id
|
||||
},
|
||||
|
||||
unreward: async (_object, params, context, _resolveInfo) => {
|
||||
const { fromBadgeId, toUserId } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let sessionRes = await session.run(
|
||||
`MATCH (badge:Badge {id: $badgeId})-[reward:REWARDED]->(rewardedUser:User {id: $rewardedUserId})
|
||||
DELETE reward
|
||||
RETURN rewardedUser {.id}`,
|
||||
{
|
||||
badgeId: fromBadgeId,
|
||||
rewardedUserId: toUserId
|
||||
}
|
||||
)
|
||||
const [rewardedUser] = sessionRes.records.map(record => {
|
||||
return record.get('rewardedUser')
|
||||
})
|
||||
session.close()
|
||||
|
||||
return rewardedUser.id
|
||||
}
|
||||
}
|
||||
}
|
||||
232
backend/src/resolvers/rewards.spec.js
Normal file
232
backend/src/resolvers/rewards.spec.js
Normal file
@ -0,0 +1,232 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login } from '../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
describe('rewards', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
id: 'u1',
|
||||
role: 'user',
|
||||
email: 'user@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
await factory.create('User', {
|
||||
id: 'u2',
|
||||
role: 'moderator',
|
||||
email: 'moderator@example.org'
|
||||
})
|
||||
await factory.create('User', {
|
||||
id: 'u3',
|
||||
role: 'admin',
|
||||
email: 'admin@example.org'
|
||||
})
|
||||
await factory.create('Badge', {
|
||||
id: 'b6',
|
||||
key: 'indiegogo_en_rhino',
|
||||
type: 'crowdfunding',
|
||||
status: 'permanent',
|
||||
icon: '/img/badges/indiegogo_en_rhino.svg'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('RewardBadge', () => {
|
||||
const mutation = `
|
||||
mutation(
|
||||
$from: ID!
|
||||
$to: ID!
|
||||
) {
|
||||
reward(fromBadgeId: $from, toUserId: $to)
|
||||
}
|
||||
`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
const variables = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
let client
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated admin', () => {
|
||||
let client
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'admin@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('rewards a badge to user', async () => {
|
||||
const variables = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
const expected = {
|
||||
reward: 'u1'
|
||||
}
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
it('rewards a second different badge to same user', async () => {
|
||||
await factory.create('Badge', {
|
||||
id: 'b1',
|
||||
key: 'indiegogo_en_racoon',
|
||||
type: 'crowdfunding',
|
||||
status: 'permanent',
|
||||
icon: '/img/badges/indiegogo_en_racoon.svg'
|
||||
})
|
||||
const variables = {
|
||||
from: 'b1',
|
||||
to: 'u1'
|
||||
}
|
||||
const expected = {
|
||||
reward: 'u1'
|
||||
}
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
it('rewards the same badge as well to another user', async () => {
|
||||
const variables1 = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
await client.request(mutation, variables1)
|
||||
|
||||
const variables2 = {
|
||||
from: 'b6',
|
||||
to: 'u2'
|
||||
}
|
||||
const expected = {
|
||||
reward: 'u2'
|
||||
}
|
||||
await expect(
|
||||
client.request(mutation, variables2)
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
it('returns the original reward if a reward is attempted a second time', async () => {
|
||||
const variables = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
await client.request(mutation, variables)
|
||||
await client.request(mutation, variables)
|
||||
|
||||
const query = `{
|
||||
User( id: "u1" ) {
|
||||
badgesCount
|
||||
}
|
||||
}
|
||||
`
|
||||
const expected = { User: [{ badgesCount: 1 }] }
|
||||
|
||||
await expect(
|
||||
client.request(query)
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated moderator', () => {
|
||||
const variables = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
let client
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe('rewards bage to user', () => {
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemoveReward', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.relate('User', 'Badges', { from: 'b6', to: 'u1' })
|
||||
})
|
||||
const variables = {
|
||||
from: 'b6',
|
||||
to: 'u1'
|
||||
}
|
||||
const expected = {
|
||||
unreward: 'u1'
|
||||
}
|
||||
|
||||
const mutation = `
|
||||
mutation(
|
||||
$from: ID!
|
||||
$to: ID!
|
||||
) {
|
||||
unreward(fromBadgeId: $from, toUserId: $to)
|
||||
}
|
||||
`
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
let client
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated admin', () => {
|
||||
let client
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'admin@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('removes a badge from user', async () => {
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
|
||||
it('fails to remove a not existing badge from user', async () => {
|
||||
await client.request(mutation, variables)
|
||||
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).rejects.toThrow('Cannot read property \'id\' of undefined')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated moderator', () => {
|
||||
let client
|
||||
beforeEach(async () => {
|
||||
const headers = await login({ email: 'moderator@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe('removes bage from user', () => {
|
||||
it('throws authorization error', async () => {
|
||||
await expect(
|
||||
client.request(mutation, variables)
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -25,6 +25,8 @@ type Mutation {
|
||||
report(id: ID!, description: String): Report
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
reward(fromBadgeId: ID!, toUserId: ID!): ID
|
||||
unreward(fromBadgeId: ID!, toUserId: ID!): ID
|
||||
"Shout the given Type and ID"
|
||||
shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId})
|
||||
@ -67,6 +69,14 @@ type Statistics {
|
||||
countShouts: Int!
|
||||
}
|
||||
|
||||
type Notification {
|
||||
id: ID!
|
||||
read: Boolean,
|
||||
user: User @relation(name: "NOTIFIED", direction: "OUT")
|
||||
post: Post @relation(name: "NOTIFIED", direction: "IN")
|
||||
createdAt: String
|
||||
}
|
||||
|
||||
scalar Date
|
||||
scalar Time
|
||||
scalar DateTime
|
||||
@ -122,6 +132,8 @@ type User {
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
notifications(read: Boolean): [Notification]! @relation(name: "NOTIFIED", direction: "IN")
|
||||
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
@ -269,6 +281,14 @@ enum FollowTypeEnum {
|
||||
Project
|
||||
}
|
||||
|
||||
type Reward {
|
||||
id: ID!
|
||||
user: User @relation(name: "REWARDED", direction: "IN")
|
||||
rewarderId: ID
|
||||
createdAt: String
|
||||
badge: Badge @relation(name: "REWARDED", direction: "OUT")
|
||||
}
|
||||
|
||||
type Organization {
|
||||
id: ID!
|
||||
createdBy: User @relation(name: "CREATED_ORGA", direction: "IN")
|
||||
|
||||
@ -8,6 +8,7 @@ import createComment from './comments.js'
|
||||
import createCategory from './categories.js'
|
||||
import createTag from './tags.js'
|
||||
import createReport from './reports.js'
|
||||
import createNotification from './notifications.js'
|
||||
|
||||
export const seedServerHost = 'http://127.0.0.1:4001'
|
||||
|
||||
@ -29,7 +30,8 @@ const factories = {
|
||||
Comment: createComment,
|
||||
Category: createCategory,
|
||||
Tag: createTag,
|
||||
Report: createReport
|
||||
Report: createReport,
|
||||
Notification: createNotification
|
||||
}
|
||||
|
||||
export const cleanDatabase = async (options = {}) => {
|
||||
|
||||
17
backend/src/seed/factories/notifications.js
Normal file
17
backend/src/seed/factories/notifications.js
Normal file
@ -0,0 +1,17 @@
|
||||
import uuid from 'uuid/v4'
|
||||
|
||||
export default function (params) {
|
||||
const {
|
||||
id = uuid(),
|
||||
read = false
|
||||
} = params
|
||||
|
||||
return `
|
||||
mutation {
|
||||
CreateNotification(
|
||||
id: "${id}",
|
||||
read: ${read},
|
||||
) { id, read }
|
||||
}
|
||||
`
|
||||
}
|
||||
@ -4,6 +4,7 @@ import uuid from 'uuid/v4'
|
||||
export default function (params) {
|
||||
const {
|
||||
id = uuid(),
|
||||
slug = '',
|
||||
title = faker.lorem.sentence(),
|
||||
content = [
|
||||
faker.lorem.sentence(),
|
||||
@ -21,6 +22,7 @@ export default function (params) {
|
||||
mutation {
|
||||
CreatePost(
|
||||
id: "${id}",
|
||||
slug: "${slug}",
|
||||
title: "${title}",
|
||||
content: "${content}",
|
||||
image: "${image}",
|
||||
|
||||
@ -5,6 +5,7 @@ export default function create (params) {
|
||||
const {
|
||||
id = uuid(),
|
||||
name = faker.name.findName(),
|
||||
slug = '',
|
||||
email = faker.internet.email(),
|
||||
password = '1234',
|
||||
role = 'user',
|
||||
@ -19,6 +20,7 @@ export default function create (params) {
|
||||
CreateUser(
|
||||
id: "${id}",
|
||||
name: "${name}",
|
||||
slug: "${slug}",
|
||||
password: "${password}",
|
||||
email: "${email}",
|
||||
avatar: "${avatar}",
|
||||
@ -29,6 +31,7 @@ export default function create (params) {
|
||||
) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
email
|
||||
avatar
|
||||
role
|
||||
|
||||
@ -13,7 +13,7 @@ import decode from './jwt/decode'
|
||||
|
||||
dotenv.config()
|
||||
// check env and warn
|
||||
const requiredEnvVars = ['MAPBOX_TOKEN', 'JWT_SECRET']
|
||||
const requiredEnvVars = ['MAPBOX_TOKEN', 'JWT_SECRET', 'PRIVATE_KEY_PASSPHRASE']
|
||||
requiredEnvVars.forEach(env => {
|
||||
if (!process.env[env]) {
|
||||
throw new Error(`ERROR: "${env}" env variable is missing.`)
|
||||
|
||||
@ -29,7 +29,7 @@ Feature: Delete an object
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/karl-heinz/status/a4DJ2afdg323v32641vna42lkj685kasd2",
|
||||
"id": "http://localhost:4123/activitypub/users/karl-heinz/status/a4DJ2afdg323v32641vna42lkj685kasd2",
|
||||
"type": "Delete",
|
||||
"object": {
|
||||
"id": "https://aronda.org/activitypub/users/bernd-das-brot/status/kljsdfg9843jknsdf234",
|
||||
|
||||
@ -15,7 +15,7 @@ Feature: Follow a user
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/stuart-little/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"id": "http://localhost:4123/activitypub/users/stuart-little/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"type": "Follow",
|
||||
"actor": "http://localhost:4123/activitypub/users/stuart-little",
|
||||
"object": "http://localhost:4123/activitypub/users/tero-vota"
|
||||
@ -32,11 +32,11 @@ Feature: Follow a user
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/tero-vota/status/a4DJ2afdg323v32641vna42lkj685kasd2",
|
||||
"id": "http://localhost:4123/activitypub/users/tero-vota/status/a4DJ2afdg323v32641vna42lkj685kasd2",
|
||||
"type": "Undo",
|
||||
"actor": "http://localhost:4123/activitypub/users/tero-vota",
|
||||
"object": {
|
||||
"id": "https://localhost:4123/activitypub/users/stuart-little/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"id": "http://localhost:4123/activitypub/users/stuart-little/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"type": "Follow",
|
||||
"actor": "http://localhost:4123/activitypub/users/stuart-little",
|
||||
"object": "http://localhost:4123/activitypub/users/tero-vota"
|
||||
|
||||
@ -13,14 +13,14 @@ Feature: Like an object like an article or note
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/karl-heinz/status/faslkasa7dasfzkjn2398hsfd",
|
||||
"id": "http://localhost:4123/activitypub/users/karl-heinz/status/faslkasa7dasfzkjn2398hsfd",
|
||||
"type": "Create",
|
||||
"actor": "https://localhost:4123/activitypub/users/karl-heinz",
|
||||
"actor": "http://localhost:4123/activitypub/users/karl-heinz",
|
||||
"object": {
|
||||
"id": "https://localhost:4123/activitypub/users/karl-heinz/status/dkasfljsdfaafg9843jknsdf",
|
||||
"id": "http://localhost:4123/activitypub/users/karl-heinz/status/dkasfljsdfaafg9843jknsdf",
|
||||
"type": "Article",
|
||||
"published": "2019-02-07T19:37:55.002Z",
|
||||
"attributedTo": "https://localhost:4123/activitypub/users/karl-heinz",
|
||||
"attributedTo": "http://localhost:4123/activitypub/users/karl-heinz",
|
||||
"content": "Hi Max, how are you?",
|
||||
"to": "https://www.w3.org/ns/activitystreams#Public"
|
||||
}
|
||||
@ -32,7 +32,7 @@ Feature: Like an object like an article or note
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/peter-lustiger/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"id": "http://localhost:4123/activitypub/users/peter-lustiger/status/83J23549sda1k72fsa4567na42312455kad83",
|
||||
"type": "Like",
|
||||
"actor": "http://localhost:4123/activitypub/users/peter-lustiger",
|
||||
"object": "http://localhost:4123/activitypub/users/karl-heinz/status/dkasfljsdfaafg9843jknsdf"
|
||||
|
||||
@ -14,10 +14,10 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
|
||||
"summary": "renate-oberdorfers outbox collection",
|
||||
"type": "OrderedCollection",
|
||||
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
|
||||
"first": "http://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
|
||||
"totalItems": 0
|
||||
}
|
||||
"""
|
||||
@ -29,10 +29,10 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/following",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/following",
|
||||
"summary": "renate-oberdorfers following collection",
|
||||
"type": "OrderedCollection",
|
||||
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
|
||||
"first": "http://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
|
||||
"totalItems": 0
|
||||
}
|
||||
"""
|
||||
@ -44,10 +44,10 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/followers",
|
||||
"summary": "renate-oberdorfers followers collection",
|
||||
"type": "OrderedCollection",
|
||||
"first": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
|
||||
"first": "http://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
|
||||
"totalItems": 0
|
||||
}
|
||||
"""
|
||||
@ -59,11 +59,11 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/outbox?page=true",
|
||||
"summary": "renate-oberdorfers outbox collection",
|
||||
"type": "OrderedCollectionPage",
|
||||
"totalItems": 0,
|
||||
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
|
||||
"partOf": "http://localhost:4123/activitypub/users/renate-oberdorfer/outbox",
|
||||
"orderedItems": []
|
||||
}
|
||||
"""
|
||||
@ -75,11 +75,11 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/following?page=true",
|
||||
"summary": "renate-oberdorfers following collection",
|
||||
"type": "OrderedCollectionPage",
|
||||
"totalItems": 0,
|
||||
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/following",
|
||||
"partOf": "http://localhost:4123/activitypub/users/renate-oberdorfer/following",
|
||||
"orderedItems": []
|
||||
}
|
||||
"""
|
||||
@ -91,11 +91,11 @@ Feature: Receiving collections
|
||||
"""
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
|
||||
"id": "http://localhost:4123/activitypub/users/renate-oberdorfer/followers?page=true",
|
||||
"summary": "renate-oberdorfers followers collection",
|
||||
"type": "OrderedCollectionPage",
|
||||
"totalItems": 0,
|
||||
"partOf": "https://localhost:4123/activitypub/users/renate-oberdorfer/followers",
|
||||
"partOf": "http://localhost:4123/activitypub/users/renate-oberdorfer/followers",
|
||||
"orderedItems": []
|
||||
}
|
||||
"""
|
||||
|
||||
@ -4,7 +4,7 @@ Feature: Webfinger discovery
|
||||
In order to follow the actor
|
||||
|
||||
Background:
|
||||
Given our own server runs at "http://localhost:4100"
|
||||
Given our own server runs at "http://localhost:4123"
|
||||
And we have the following users in our database:
|
||||
| Slug |
|
||||
| peter-lustiger |
|
||||
@ -19,7 +19,7 @@ Feature: Webfinger discovery
|
||||
{
|
||||
"rel": "self",
|
||||
"type": "application/activity+json",
|
||||
"href": "https://localhost:4123/users/peter-lustiger"
|
||||
"href": "http://localhost:4123/activitypub/users/peter-lustiger"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -44,21 +44,21 @@ Feature: Webfinger discovery
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1"
|
||||
],
|
||||
"id": "https://localhost:4123/activitypub/users/peter-lustiger",
|
||||
"id": "http://localhost:4123/activitypub/users/peter-lustiger",
|
||||
"type": "Person",
|
||||
"preferredUsername": "peter-lustiger",
|
||||
"name": "peter-lustiger",
|
||||
"following": "https://localhost:4123/activitypub/users/peter-lustiger/following",
|
||||
"followers": "https://localhost:4123/activitypub/users/peter-lustiger/followers",
|
||||
"inbox": "https://localhost:4123/activitypub/users/peter-lustiger/inbox",
|
||||
"outbox": "https://localhost:4123/activitypub/users/peter-lustiger/outbox",
|
||||
"url": "https://localhost:4123/activitypub/@peter-lustiger",
|
||||
"following": "http://localhost:4123/activitypub/users/peter-lustiger/following",
|
||||
"followers": "http://localhost:4123/activitypub/users/peter-lustiger/followers",
|
||||
"inbox": "http://localhost:4123/activitypub/users/peter-lustiger/inbox",
|
||||
"outbox": "http://localhost:4123/activitypub/users/peter-lustiger/outbox",
|
||||
"url": "http://localhost:4123/activitypub/@peter-lustiger",
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://localhost:4123/activitypub/inbox"
|
||||
"sharedInbox": "http://localhost:4123/activitypub/inbox"
|
||||
},
|
||||
"publicKey": {
|
||||
"id": "https://localhost:4123/activitypub/users/peter-lustiger#main-key",
|
||||
"owner": "https://localhost:4123/activitypub/users/peter-lustiger",
|
||||
"id": "http://localhost:4123/activitypub/users/peter-lustiger#main-key",
|
||||
"owner": "http://localhost:4123/activitypub/users/peter-lustiger",
|
||||
"publicKeyPem": "adglkjlk89235kjn8obn2384f89z5bv9..."
|
||||
}
|
||||
}
|
||||
|
||||
1535
backend/yarn.lock
1535
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ Feature: Search
|
||||
And we have the following posts in our database:
|
||||
| Author | id | title | content |
|
||||
| Brianna Wiest | p1 | 101 Essays that will change the way you think | 101 Essays, of course! |
|
||||
| Brianna Wiest | p1 | No searched for content | will be found in this post, I guarantee |
|
||||
| Brianna Wiest | p2 | No searched for content | will be found in this post, I guarantee |
|
||||
Given I am logged in
|
||||
|
||||
Scenario: Search for specific words
|
||||
|
||||
@ -17,7 +17,7 @@ Feature: Create a post
|
||||
for active citizenship.
|
||||
"""
|
||||
And I click on "Save"
|
||||
Then I get redirected to "/post/my-first-post/"
|
||||
Then I get redirected to ".../my-first-post"
|
||||
And the post was saved successfully
|
||||
|
||||
Scenario: See a post on the landing page
|
||||
|
||||
@ -21,8 +21,8 @@ Feature: Tags and Categories
|
||||
Scenario: See an overview of categories
|
||||
When I navigate to the administration dashboard
|
||||
And I click on the menu item "Categories"
|
||||
Then I can see a list of categories ordered by post count:
|
||||
| Icon | Name | Posts |
|
||||
Then I can see the following table:
|
||||
| | Name | Posts |
|
||||
| | Just For Fun | 2 |
|
||||
| | Happyness & Values | 1 |
|
||||
| | Health & Wellbeing | 0 |
|
||||
@ -30,11 +30,8 @@ Feature: Tags and Categories
|
||||
Scenario: See an overview of tags
|
||||
When I navigate to the administration dashboard
|
||||
And I click on the menu item "Tags"
|
||||
Then I can see a list of tags ordered by user count:
|
||||
| # | Name | Users | Posts |
|
||||
Then I can see the following table:
|
||||
| | Name | Users | Posts |
|
||||
| 1 | Democracy | 2 | 3 |
|
||||
| 2 | Ecology | 1 | 1 |
|
||||
| 3 | Nature | 1 | 2 |
|
||||
|
||||
|
||||
|
||||
@ -9,38 +9,13 @@ When('I navigate to the administration dashboard', () => {
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I can see a list of categories ordered by post count:', table => {
|
||||
cy.get('thead')
|
||||
.find('tr th')
|
||||
.should('have.length', 3)
|
||||
table.hashes().forEach(({ Name, Posts }, index) => {
|
||||
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`).should(
|
||||
'contain',
|
||||
Name.trim()
|
||||
)
|
||||
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`).should(
|
||||
'contain',
|
||||
Posts
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Then('I can see a list of tags ordered by user count:', table => {
|
||||
cy.get('thead')
|
||||
.find('tr th')
|
||||
.should('have.length', 4)
|
||||
table.hashes().forEach(({ Name, Users, Posts }, index) => {
|
||||
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`).should(
|
||||
'contain',
|
||||
Name.trim()
|
||||
)
|
||||
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`).should(
|
||||
'contain',
|
||||
Users
|
||||
)
|
||||
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(4)`).should(
|
||||
'contain',
|
||||
Posts
|
||||
)
|
||||
Then('I can see the following table:', table => {
|
||||
const headers = table.raw()[0]
|
||||
headers.forEach((expected, i) => {
|
||||
cy.get('thead th').eq(i).should('contain', expected)
|
||||
})
|
||||
const flattened = [].concat.apply([], table.rows())
|
||||
flattened.forEach((expected, i) => {
|
||||
cy.get('tbody td').eq(i).should('contain', expected)
|
||||
})
|
||||
})
|
||||
|
||||
@ -39,10 +39,10 @@ Given('I am logged in with a {string} role', role => {
|
||||
})
|
||||
})
|
||||
|
||||
When('I click on "Report Post" from the triple dot menu of the post', () => {
|
||||
When('I click on "Report Post" from the content menu of the post', () => {
|
||||
cy.contains('.ds-card', davidIrvingPostTitle)
|
||||
.find('.content-menu-trigger')
|
||||
.click()
|
||||
.click({force: true})
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
.contains('Report Post')
|
||||
@ -50,7 +50,7 @@ When('I click on "Report Post" from the triple dot menu of the post', () => {
|
||||
})
|
||||
|
||||
When(
|
||||
'I click on "Report User" from the triple dot menu in the user info box',
|
||||
'I click on "Report User" from the content menu in the user info box',
|
||||
() => {
|
||||
cy.contains('.ds-card', davidIrvingName)
|
||||
.find('.content-menu-trigger')
|
||||
@ -71,7 +71,7 @@ When('I click on the author', () => {
|
||||
})
|
||||
|
||||
When('I report the author', () => {
|
||||
cy.get('.page-name-profile-slug').then(() => {
|
||||
cy.get('.page-name-profile-id-slug').then(() => {
|
||||
invokeReportOnElement('.ds-card').then(() => {
|
||||
cy.get('button')
|
||||
.contains('Send')
|
||||
|
||||
@ -42,9 +42,13 @@ When('I select an entry', () => {
|
||||
})
|
||||
|
||||
Then("I should be on the post's page", () => {
|
||||
cy.location('pathname').should(
|
||||
'contain',
|
||||
'/post/'
|
||||
)
|
||||
cy.location('pathname').should(
|
||||
'eq',
|
||||
'/post/101-essays-that-will-change-the-way-you-think/'
|
||||
'/post/p1/101-essays-that-will-change-the-way-you-think'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { getLangByName } from '../../support/helpers'
|
||||
|
||||
let lastPost = {}
|
||||
|
||||
const loginCredentials = {
|
||||
let loginCredentials = {
|
||||
email: 'peterpan@example.org',
|
||||
password: '1234'
|
||||
}
|
||||
@ -86,6 +86,10 @@ Given('my user account has the role {string}', role => {
|
||||
|
||||
When('I log out', cy.logout)
|
||||
|
||||
When('I visit {string}', page => {
|
||||
cy.openPage(page)
|
||||
})
|
||||
|
||||
When('I visit the {string} page', page => {
|
||||
cy.openPage(page)
|
||||
})
|
||||
@ -220,7 +224,7 @@ Then('the post shows up on the landing page at position {int}', index => {
|
||||
})
|
||||
|
||||
Then('I get redirected to {string}', route => {
|
||||
cy.location('pathname').should('contain', route)
|
||||
cy.location('pathname').should('contain', route.replace('...', ''))
|
||||
})
|
||||
|
||||
Then('the post was saved successfully', () => {
|
||||
@ -244,3 +248,48 @@ Then(
|
||||
cy.get('.error').should('contain', message)
|
||||
}
|
||||
)
|
||||
|
||||
Given('my user account has the following login credentials:', table => {
|
||||
loginCredentials = table.hashes()[0]
|
||||
cy.debug()
|
||||
cy.factory().create('User', loginCredentials)
|
||||
})
|
||||
|
||||
When('I fill the password form with:', table => {
|
||||
table = table.rowsHash()
|
||||
cy.get('input[id=oldPassword]')
|
||||
.type(table['Your old password'])
|
||||
.get('input[id=newPassword]')
|
||||
.type(table['Your new passsword'])
|
||||
.get('input[id=confirmPassword]')
|
||||
.type(table['Confirm new password'])
|
||||
})
|
||||
|
||||
When('submit the form', () => {
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
Then('I cannot login anymore with password {string}', password => {
|
||||
cy.reload()
|
||||
const { email } = loginCredentials
|
||||
cy.visit(`/login`)
|
||||
cy.get('input[name=email]')
|
||||
.trigger('focus')
|
||||
.type(email)
|
||||
cy.get('input[name=password]')
|
||||
.trigger('focus')
|
||||
.type(password)
|
||||
cy.get('button[name=submit]')
|
||||
.as('submitButton')
|
||||
.click()
|
||||
cy.get('.iziToast-wrapper').should('contain', 'Incorrect email address or password.')
|
||||
})
|
||||
|
||||
Then('I can login successfully with password {string}', password => {
|
||||
cy.reload()
|
||||
cy.login({
|
||||
...loginCredentials,
|
||||
...{password}
|
||||
})
|
||||
cy.get('.iziToast-wrapper').should('contain', "You are logged in!")
|
||||
})
|
||||
|
||||
41
cypress/integration/identifier/PersistentLinks.feature
Normal file
41
cypress/integration/identifier/PersistentLinks.feature
Normal file
@ -0,0 +1,41 @@
|
||||
Feature: Persistent Links
|
||||
As a user
|
||||
I want all links to carry permanent information that identifies the linked resource
|
||||
In order to have persistent links even if a part of the URL might change
|
||||
|
||||
| | Modifiable | Referenceable | Unique | Purpose |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| ID | no | yes | yes | Identity, Traceability, Links |
|
||||
| Slug | yes | yes | yes | @-Mentions, SEO-friendly URL |
|
||||
| Name | yes | no | no | Search, self-description |
|
||||
|
||||
|
||||
Background:
|
||||
Given we have the following user accounts:
|
||||
| id | name | slug |
|
||||
| MHNqce98y1 | Stephen Hawking | thehawk |
|
||||
And we have the following posts in our database:
|
||||
| id | title | slug |
|
||||
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays |
|
||||
And I have a user account
|
||||
And I am logged in
|
||||
|
||||
Scenario Outline: Link with slug only is valid and gets auto-completed
|
||||
When I visit "<url>"
|
||||
Then I get redirected to "<redirectUrl>"
|
||||
Examples:
|
||||
| url | redirectUrl |
|
||||
| /profile/thehawk | /profile/MHNqce98y1/thehawk |
|
||||
| /post/101-essays | /post/bWBjpkTKZp/101-essays |
|
||||
|
||||
Scenario: Link with id only will always point to the same user
|
||||
When I visit "/profile/MHNqce98y1"
|
||||
Then I get redirected to "/profile/MHNqce98y1/thehawk"
|
||||
|
||||
Scenario Outline: ID takes precedence over slug
|
||||
When I visit "<url>"
|
||||
Then I get redirected to "<redirectUrl>"
|
||||
Examples:
|
||||
| url | redirectUrl |
|
||||
| /profile/MHNqce98y1/stephen-hawking | /profile/MHNqce98y1/thehawk |
|
||||
| /post/bWBjpkTKZp/the-way-you-think | /post/bWBjpkTKZp/101-essays |
|
||||
@ -15,7 +15,7 @@ Feature: Report and Moderate
|
||||
Scenario Outline: Report a post from various pages
|
||||
Given I am logged in with a "user" role
|
||||
When I see David Irving's post on the <Page>
|
||||
And I click on "Report Post" from the triple dot menu of the post
|
||||
And I click on "Report Post" from the content menu of the post
|
||||
And I confirm the reporting dialog because it is a criminal act under German law:
|
||||
"""
|
||||
Do you really want to report the contribution "The Truth about the Holocaust"?
|
||||
@ -33,7 +33,7 @@ Feature: Report and Moderate
|
||||
Given I am logged in with a "user" role
|
||||
And I see David Irving's post on the post page
|
||||
When I click on the author
|
||||
And I click on "Report User" from the triple dot menu in the user info box
|
||||
And I click on "Report User" from the content menu in the user info box
|
||||
And I confirm the reporting dialog because he is a holocaust denier:
|
||||
"""
|
||||
Do you really want to report the user "David Irving"?
|
||||
|
||||
31
cypress/integration/settings/ChangePassword.feature
Normal file
31
cypress/integration/settings/ChangePassword.feature
Normal file
@ -0,0 +1,31 @@
|
||||
Feature: Change password
|
||||
As a user
|
||||
I want to change my password in my settings
|
||||
For security, e.g. if I exposed my password by accident
|
||||
|
||||
Login via email and password is a well-known authentication procedure and you
|
||||
can assure to the server that you are who you claim to be. Either if you
|
||||
exposed your password by acccident and you want to invalidate the exposed
|
||||
password or just out of an good habit, you want to change your password.
|
||||
|
||||
Background:
|
||||
Given my user account has the following login credentials:
|
||||
| email | password |
|
||||
| user@example.org | exposed |
|
||||
And I am logged in
|
||||
|
||||
Scenario: Change my password
|
||||
Given I am on the "settings" page
|
||||
And I click on "Security"
|
||||
When I fill the password form with:
|
||||
| Your old password | exposed |
|
||||
| Your new passsword | secure |
|
||||
| Confirm new password | secure |
|
||||
And submit the form
|
||||
And I see a success message:
|
||||
"""
|
||||
Password successfully changed!
|
||||
"""
|
||||
And I log out through the menu in the top right corner
|
||||
Then I cannot login anymore with password "exposed"
|
||||
But I can login successfully with password "secure"
|
||||
@ -1,25 +0,0 @@
|
||||
language: generic
|
||||
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_87342d90efbe_key -iv $encrypted_87342d90efbe_iv
|
||||
-in kubeconfig.yaml.enc -out kubeconfig.yaml -d
|
||||
|
||||
install:
|
||||
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
- chmod +x ./kubectl
|
||||
- sudo mv ./kubectl /usr/local/bin/kubectl
|
||||
- mkdir ${HOME}/.kube
|
||||
- cp kubeconfig.yaml ${HOME}/.kube/config
|
||||
|
||||
script:
|
||||
- kubectl get nodes
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
# TODO: fix downtime
|
||||
# instead of deleting all pods, update the deployment and make a rollout
|
||||
# TODO: fix multiple access error on volumes
|
||||
# this happens if more than two pods access a volume
|
||||
script: kubectl --namespace=human-connection delete pods --all
|
||||
on:
|
||||
branch: master
|
||||
@ -10,6 +10,7 @@
|
||||
NEO4J_AUTH: "none"
|
||||
CLIENT_URI: "https://nitro-staging.human-connection.org"
|
||||
MAPBOX_TOKEN: "pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
|
||||
PRIVATE_KEY_PASSPHRASE: "a7dsf78sadg87ad87sfagsadg78"
|
||||
metadata:
|
||||
name: configmap
|
||||
namespace: human-connection
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
metadata:
|
||||
name: nitro-backend
|
||||
namespace: human-connection
|
||||
labels:
|
||||
commit: "COMMIT"
|
||||
spec:
|
||||
replicas: 1
|
||||
minReadySeconds: 15
|
||||
@ -20,6 +18,7 @@
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
human-connection.org/commit: COMMIT
|
||||
human-connection.org/selector: deployment-human-connection-backend
|
||||
name: "nitro-backend"
|
||||
spec:
|
||||
|
||||
@ -3,8 +3,6 @@ kind: Deployment
|
||||
metadata:
|
||||
name: nitro-web
|
||||
namespace: human-connection
|
||||
labels:
|
||||
commit: "COMMIT"
|
||||
spec:
|
||||
replicas: 2
|
||||
minReadySeconds: 15
|
||||
@ -15,6 +13,7 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
human-connection.org/commit: COMMIT
|
||||
human-connection.org/selector: deployment-human-connection-web
|
||||
name: nitro-web
|
||||
spec:
|
||||
|
||||
Binary file not shown.
@ -12,7 +12,7 @@ services:
|
||||
context: webapp
|
||||
target: build-and-test
|
||||
environment:
|
||||
- GRAPHQL_URI=http://backend:4123
|
||||
- GRAPHQL_URI=http://backend:4000
|
||||
backend:
|
||||
image: humanconnection/nitro-backend:builder
|
||||
build:
|
||||
|
||||
12
package.json
12
package.json
@ -8,15 +8,21 @@
|
||||
"nonGlobalStepDefinitions": true
|
||||
},
|
||||
"scripts": {
|
||||
"cypress:run": "cypress run --browser chrome",
|
||||
"cypress:open": "cypress open --browser chrome"
|
||||
"cypress:backend:server": "cd backend && yarn run test:before:server",
|
||||
"cypress:backend:seeder": "cd backend && yarn run test:before:seeder",
|
||||
"cypress:webapp": "cd webapp && cross-env GRAPHQL_URI=http://localhost:4123 yarn run dev",
|
||||
"cypress:setup": "run-p cypress:backend:* cypress:webapp",
|
||||
"cypress:run": "cypress run --browser chromium",
|
||||
"cypress:open": "cypress open --browser chromium"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.2.0",
|
||||
"cypress-cucumber-preprocessor": "^1.11.0",
|
||||
"dotenv": "^7.0.0",
|
||||
"faker": "^4.1.0",
|
||||
"graphql-request": "^1.8.2",
|
||||
"neo4j-driver": "^1.7.3"
|
||||
"neo4j-driver": "^1.7.3",
|
||||
"npm-run-all": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
sed -i "s/<COMMIT>/${TRAVIS_COMMIT}/g" patch-deployment.yaml
|
||||
kubectl --namespace=human-connection patch deployment nitro-backend -p "$(cat patch-deployment.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment nitro-web -p "$(cat patch-deployment.yaml)"
|
||||
sed -i "s/<COMMIT>/${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patch-deployment.yaml
|
||||
kubectl --namespace=human-connection patch deployment nitro-backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patch-deployment.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment nitro-web -p "$(cat $TRAVIS_BUILD_DIR/scripts/patch-deployment.yaml)"
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
metadata:
|
||||
labels:
|
||||
commit: <COMMIT>
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
human-connection.org/commit: <COMMIT>
|
||||
|
||||
18
scripts/setup_kubernetes.sh
Executable file
18
scripts/setup_kubernetes.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script can be called multiple times for each `before_deploy` hook
|
||||
# so let's exit successfully if kubectl is already installed:
|
||||
command -v kubectl && exit 0
|
||||
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
chmod +x ./kubectl
|
||||
sudo mv ./kubectl /usr/local/bin/kubectl
|
||||
|
||||
curl -LO https://github.com/digitalocean/doctl/releases/download/v1.14.0/doctl-1.14.0-linux-amd64.tar.gz
|
||||
tar xf doctl-1.14.0-linux-amd64.tar.gz
|
||||
chmod +x ./doctl
|
||||
sudo mv ./doctl /usr/local/bin/doctl
|
||||
|
||||
doctl auth init --access-token $DOCTL_ACCESS_TOKEN
|
||||
mkdir -p ~/.kube/
|
||||
doctl kubernetes cluster kubeconfig show nitro-staging > ~/.kube/config
|
||||
154
webapp/components/ChangePassword.spec.js
Normal file
154
webapp/components/ChangePassword.spec.js
Normal file
@ -0,0 +1,154 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import ChangePassword from './ChangePassword.vue'
|
||||
import Vue from 'vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('ChangePassword.vue', () => {
|
||||
let mocks
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
validate: jest.fn(),
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn()
|
||||
},
|
||||
$t: jest.fn(),
|
||||
$store: {
|
||||
commit: jest.fn()
|
||||
},
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockRejectedValue({ message: 'Ouch!' })
|
||||
.mockResolvedValueOnce({ data: { changePassword: 'NEWTOKEN' } })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let wrapper
|
||||
const Wrapper = () => {
|
||||
return mount(ChangePassword, { mocks, localVue })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders three input fields', () => {
|
||||
expect(wrapper.findAll('input')).toHaveLength(3)
|
||||
})
|
||||
|
||||
describe('validations', () => {
|
||||
it('invalid', () => {
|
||||
expect(wrapper.vm.disabled).toBe(true)
|
||||
})
|
||||
|
||||
describe('old password and new password', () => {
|
||||
describe('match', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input#oldPassword').setValue('some secret')
|
||||
wrapper.find('input#newPassword').setValue('some secret')
|
||||
})
|
||||
|
||||
it('invalid', () => {
|
||||
expect(wrapper.vm.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it.skip('displays a warning', () => {
|
||||
const calls = mocks.validate.mock.calls
|
||||
const expected = [
|
||||
['change-password.validations.old-and-new-password-match']
|
||||
]
|
||||
expect(calls).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new password and confirmation', () => {
|
||||
describe('mismatch', () => {
|
||||
it.todo('invalid')
|
||||
it.todo('displays a warning')
|
||||
})
|
||||
|
||||
describe('match', () => {
|
||||
describe('and old password mismatch', () => {
|
||||
it.todo('valid')
|
||||
})
|
||||
|
||||
describe('clicked', () => {
|
||||
it.todo('sets loading')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given valid input', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input#oldPassword').setValue('supersecret')
|
||||
wrapper.find('input#newPassword').setValue('superdupersecret')
|
||||
wrapper.find('input#confirmPassword').setValue('superdupersecret')
|
||||
})
|
||||
|
||||
describe('submit form', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('calls changePassword mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('passes form data as variables', () => {
|
||||
expect(mocks.$apollo.mutate.mock.calls[0][0]).toEqual(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
oldPassword: 'supersecret',
|
||||
newPassword: 'superdupersecret',
|
||||
confirmPassword: 'superdupersecret'
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe('mutation resolves', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue()
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('calls auth/SET_TOKEN with response', () => {
|
||||
expect(mocks.$store.commit).toHaveBeenCalledWith(
|
||||
'auth/SET_TOKEN',
|
||||
'NEWTOKEN'
|
||||
)
|
||||
})
|
||||
|
||||
it('displays success message', () => {
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'settings.security.change-password.success'
|
||||
)
|
||||
expect(mocks.$toast.success).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutation rejects', () => {
|
||||
beforeEach(() => {
|
||||
// second call will reject
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('displays error message', () => {
|
||||
expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
83
webapp/components/ChangePassword.vue
Normal file
83
webapp/components/ChangePassword.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<template>
|
||||
<ds-input
|
||||
id="oldPassword"
|
||||
model="oldPassword"
|
||||
type="password"
|
||||
label="Your old password"
|
||||
/>
|
||||
<ds-input
|
||||
id="newPassword"
|
||||
model="newPassword"
|
||||
type="password"
|
||||
label="Your new password"
|
||||
/>
|
||||
<ds-input
|
||||
id="confirmPassword"
|
||||
model="confirmPassword"
|
||||
type="password"
|
||||
label="Confirm new password"
|
||||
/>
|
||||
<ds-space margin-top="base">
|
||||
<ds-button
|
||||
:loading="loading"
|
||||
primary
|
||||
>
|
||||
{{ $t('settings.security.change-password.button') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</template>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
name: 'ChangePassword',
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
formSchema: {
|
||||
oldPassword: { required: true },
|
||||
newPassword: { required: true },
|
||||
confirmPassword: { required: true }
|
||||
},
|
||||
loading: false,
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleSubmit(data) {
|
||||
this.loading = true
|
||||
const mutation = gql`
|
||||
mutation($oldPassword: String!, $newPassword: String!) {
|
||||
changePassword(oldPassword: $oldPassword, newPassword: $newPassword)
|
||||
}
|
||||
`
|
||||
const variables = this.formData
|
||||
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({ mutation, variables })
|
||||
this.$store.commit('auth/SET_TOKEN', data.changePassword)
|
||||
this.$toast.success(
|
||||
this.$t('settings.security.change-password.success')
|
||||
)
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -111,8 +111,8 @@ export default {
|
||||
const result = res.data[this.id ? 'UpdatePost' : 'CreatePost']
|
||||
|
||||
this.$router.push({
|
||||
name: 'post-slug',
|
||||
params: { slug: result.slug }
|
||||
name: 'post-id-slug',
|
||||
params: { id: result.id, slug: result.slug }
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@ -106,8 +106,8 @@ export default {
|
||||
methods: {
|
||||
href(post) {
|
||||
return this.$router.resolve({
|
||||
name: 'post-slug',
|
||||
params: { slug: post.slug }
|
||||
name: 'post-id-slug',
|
||||
params: { id: post.id, slug: post.slug }
|
||||
}).href
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,9 +153,9 @@ export default {
|
||||
return count
|
||||
},
|
||||
userLink() {
|
||||
const { slug } = this.user
|
||||
if (!slug) return ''
|
||||
return { name: 'profile-slug', params: { slug } }
|
||||
const { id, slug } = this.user
|
||||
if (!(id && slug)) return ''
|
||||
return { name: 'profile-id-slug', params: { slug, id } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,58 +9,69 @@ export default app => {
|
||||
type
|
||||
createdAt
|
||||
submitter {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
name
|
||||
slug
|
||||
}
|
||||
user {
|
||||
name
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
disabledBy {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
comment {
|
||||
contentExcerpt
|
||||
author {
|
||||
name
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
post {
|
||||
id
|
||||
slug
|
||||
title
|
||||
disabled
|
||||
deleted
|
||||
title
|
||||
slug
|
||||
}
|
||||
disabledBy {
|
||||
disabled
|
||||
deleted
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
post {
|
||||
title
|
||||
id
|
||||
slug
|
||||
title
|
||||
disabled
|
||||
deleted
|
||||
author {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
name
|
||||
slug
|
||||
}
|
||||
disabledBy {
|
||||
disabled
|
||||
deleted
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ export default app => {
|
||||
query User($slug: String!, $first: Int, $offset: Int) {
|
||||
User(slug: $slug) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
about
|
||||
@ -27,8 +28,8 @@ export default app => {
|
||||
followingCount
|
||||
following(first: 7) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
name
|
||||
avatar
|
||||
disabled
|
||||
deleted
|
||||
@ -49,10 +50,10 @@ export default app => {
|
||||
followedByCurrentUser
|
||||
followedBy(first: 7) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
disabled
|
||||
deleted
|
||||
slug
|
||||
avatar
|
||||
followedByCount
|
||||
followedByCurrentUser
|
||||
@ -87,6 +88,7 @@ export default app => {
|
||||
}
|
||||
author {
|
||||
id
|
||||
slug
|
||||
avatar
|
||||
name
|
||||
disabled
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
>
|
||||
<a
|
||||
class="avatar-menu-trigger"
|
||||
:href="$router.resolve({name: 'profile-slug', params: {slug: user.slug}}).href"
|
||||
:href="$router.resolve({name: 'profile-id-slug', params: {id: user.id, slug: user.slug}}).href"
|
||||
@click.prevent="toggleMenu"
|
||||
>
|
||||
<ds-avatar
|
||||
@ -182,8 +182,8 @@ export default {
|
||||
goToPost(item) {
|
||||
this.$nextTick(() => {
|
||||
this.$router.push({
|
||||
name: 'post-slug',
|
||||
params: { slug: item.slug }
|
||||
name: 'post-id-slug',
|
||||
params: { id: item.id, slug: item.slug }
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@ -31,7 +31,11 @@
|
||||
"labelBio": "Über dich"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sicherheit"
|
||||
"name": "Sicherheit",
|
||||
"change-password": {
|
||||
"button": "Passwort ändern",
|
||||
"success": "Passwort erfolgreich geändert!"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Einladungen"
|
||||
|
||||
@ -31,7 +31,11 @@
|
||||
"labelBio": "About You"
|
||||
},
|
||||
"security": {
|
||||
"name": "Security"
|
||||
"name": "Security",
|
||||
"change-password": {
|
||||
"button": "Change password",
|
||||
"success": "Password successfully changed!"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invites"
|
||||
|
||||
32
webapp/mixins/persistentLinks.js
Normal file
32
webapp/mixins/persistentLinks.js
Normal file
@ -0,0 +1,32 @@
|
||||
export default function(options = {}) {
|
||||
const { queryId, querySlug, path, message = 'Page not found.' } = options
|
||||
return {
|
||||
asyncData: async context => {
|
||||
const {
|
||||
params: { id, slug },
|
||||
redirect,
|
||||
error,
|
||||
app: { apolloProvider }
|
||||
} = context
|
||||
const idOrSlug = id || slug
|
||||
|
||||
const variables = { idOrSlug }
|
||||
const client = apolloProvider.defaultClient
|
||||
|
||||
let response
|
||||
let resource
|
||||
response = await client.query({ query: queryId, variables })
|
||||
resource = response.data[Object.keys(response.data)[0]][0]
|
||||
if (resource && resource.slug === slug) return // all good
|
||||
if (resource && resource.slug !== slug) {
|
||||
return redirect(`/${path}/${resource.id}/${resource.slug}`)
|
||||
}
|
||||
|
||||
response = await client.query({ query: querySlug, variables })
|
||||
resource = response.data[Object.keys(response.data)[0]][0]
|
||||
if (resource) return redirect(`/${path}/${resource.id}/${resource.slug}`)
|
||||
|
||||
return error({ statusCode: 404, message })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,12 +121,30 @@ module.exports = {
|
||||
proxy: true
|
||||
},
|
||||
proxy: {
|
||||
'/.well-known/webfinger': {
|
||||
target: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
||||
toProxy: true, // cloudflare needs that
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-UI-Request': true,
|
||||
'X-API-TOKEN': process.env.BACKEND_TOKEN || 'NULL'
|
||||
}
|
||||
},
|
||||
'/activitypub': {
|
||||
// make this configurable (nuxt-dotenv)
|
||||
target: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
||||
toProxy: true, // cloudflare needs that
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-UI-Request': true,
|
||||
'X-API-TOKEN': process.env.BACKEND_TOKEN || 'NULL'
|
||||
}
|
||||
},
|
||||
'/api': {
|
||||
// make this configurable (nuxt-dotenv)
|
||||
target: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
||||
pathRewrite: { '^/api': '' },
|
||||
toProxy: true, // cloudflare needs that
|
||||
changeOrigin: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-UI-Request': true,
|
||||
|
||||
@ -11,10 +11,8 @@
|
||||
"start": "cross-env node server/index.js",
|
||||
"generate": "nuxt generate",
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
"test": "jest",
|
||||
"precommit": "yarn lint",
|
||||
"e2e:local": "cypress run --headed",
|
||||
"e2e:ci": "npm-run-all --parallel --race start:ci 'cypress:ci --config baseUrl=http://localhost:3000'",
|
||||
"test": "jest",
|
||||
"test:unit:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --no-cache --runInBand"
|
||||
},
|
||||
"jest": {
|
||||
@ -46,7 +44,7 @@
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.27",
|
||||
"express": "~4.16.4",
|
||||
"graphql": "~14.1.1",
|
||||
"graphql": "~14.2.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkify-it": "~2.1.0",
|
||||
"nuxt": "~2.4.5",
|
||||
@ -61,26 +59,26 @@
|
||||
"vuex-i18n": "~1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.3.4",
|
||||
"@babel/preset-env": "~7.3.4",
|
||||
"@vue/cli-shared-utils": "~3.4.1",
|
||||
"@babel/core": "~7.4.3",
|
||||
"@babel/preset-env": "~7.4.3",
|
||||
"@vue/cli-shared-utils": "~3.5.1",
|
||||
"@vue/eslint-config-prettier": "~4.0.1",
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
"babel-core": "~7.0.0-bridge.0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-jest": "~24.5.0",
|
||||
"eslint": "~5.15.1",
|
||||
"eslint-config-prettier": "~3.6.0",
|
||||
"babel-jest": "~24.7.1",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~4.1.0",
|
||||
"eslint-loader": "~2.1.2",
|
||||
"eslint-plugin-prettier": "~3.0.1",
|
||||
"eslint-plugin-vue": "~5.2.2",
|
||||
"jest": "~24.5.0",
|
||||
"jest": "~24.7.1",
|
||||
"node-sass": "~4.11.0",
|
||||
"nodemon": "~1.18.10",
|
||||
"prettier": "~1.14.3",
|
||||
"sass-loader": "~7.1.0",
|
||||
"vue-jest": "~3.0.4",
|
||||
"vue-svg-loader": "~0.11.0"
|
||||
"vue-svg-loader": "~0.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,8 +64,8 @@ export default {
|
||||
},
|
||||
href(post) {
|
||||
return this.$router.resolve({
|
||||
name: 'post-slug',
|
||||
params: { slug: post.slug }
|
||||
name: 'post-id-slug',
|
||||
params: { id: post.id, slug: post.slug }
|
||||
}).href
|
||||
},
|
||||
showMoreContributions() {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
slot-scope="scope"
|
||||
>
|
||||
<div v-if="scope.row.type === 'Post'">
|
||||
<nuxt-link :to="{ name: 'post-slug', params: { slug: scope.row.post.slug } }">
|
||||
<nuxt-link :to="{ name: 'post-id-slug', params: { id: scope.row.post.id, slug: scope.row.post.slug } }">
|
||||
<b>{{ scope.row.post.title | truncate(50) }}</b>
|
||||
</nuxt-link><br>
|
||||
<ds-text
|
||||
@ -25,7 +25,7 @@
|
||||
</ds-text>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'Comment'">
|
||||
<nuxt-link :to="{ name: 'post-slug', params: { slug: scope.row.comment.post.slug } }">
|
||||
<nuxt-link :to="{ name: 'post-id-slug', params: { id: scope.row.comment.post.id, slug: scope.row.comment.post.slug } }">
|
||||
<b>{{ scope.row.comment.contentExcerpt | truncate(50) }}</b>
|
||||
</nuxt-link><br>
|
||||
<ds-text
|
||||
@ -36,7 +36,7 @@
|
||||
</ds-text>
|
||||
</div>
|
||||
<div v-else>
|
||||
<nuxt-link :to="{ name: 'profile-slug', params: { slug: scope.row.user.slug } }">
|
||||
<nuxt-link :to="{ name: 'profile-id-slug', params: { id: scope.row.user.id, slug: scope.row.user.slug } }">
|
||||
<b>{{ scope.row.user.name | truncate(50) }}</b>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
slot="submitter"
|
||||
slot-scope="scope"
|
||||
>
|
||||
<nuxt-link :to="{ name: 'profile-slug', params: { slug: scope.row.submitter.slug } }">
|
||||
<nuxt-link :to="{ name: 'profile-id-slug', params: { id: scope.row.submitter.id, slug: scope.row.submitter.slug } }">
|
||||
{{ scope.row.submitter.name }}
|
||||
</nuxt-link>
|
||||
</template>
|
||||
@ -79,19 +79,19 @@
|
||||
>
|
||||
<nuxt-link
|
||||
v-if="scope.row.type === 'Post' && scope.row.post.disabledBy"
|
||||
:to="{ name: 'profile-slug', params: { slug: scope.row.post.disabledBy.slug } }"
|
||||
:to="{ name: 'profile-id-slug', params: { id: scope.row.post.disabledBy.id, slug: scope.row.post.disabledBy.slug } }"
|
||||
>
|
||||
<b>{{ scope.row.post.disabledBy.name | truncate(50) }}</b>
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
v-else-if="scope.row.type === 'Comment' && scope.row.comment.disabledBy"
|
||||
:to="{ name: 'profile-slug', params: { slug: scope.row.comment.disabledBy.slug } }"
|
||||
:to="{ name: 'profile-id-slug', params: { id: scope.row.comment.disabledBy.id, slug: scope.row.comment.disabledBy.slug } }"
|
||||
>
|
||||
<b>{{ scope.row.comment.disabledBy.name | truncate(50) }}</b>
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
v-else-if="scope.row.type === 'User' && scope.row.user.disabledBy"
|
||||
:to="{ name: 'profile-slug', params: { slug: scope.row.user.disabledBy.slug } }"
|
||||
:to="{ name: 'profile-id-slug', params: { id: scope.row.user.disabledBy.id, slug: scope.row.user.disabledBy.slug } }"
|
||||
>
|
||||
<b>{{ scope.row.user.disabledBy.name | truncate(50) }}</b>
|
||||
</nuxt-link>
|
||||
|
||||
@ -17,35 +17,62 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import PersistentLinks from '~/mixins/persistentLinks.js'
|
||||
|
||||
const options = {
|
||||
queryId: gql`
|
||||
query($idOrSlug: ID) {
|
||||
Post(id: $idOrSlug) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`,
|
||||
querySlug: gql`
|
||||
query($idOrSlug: String) {
|
||||
Post(slug: $idOrSlug) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`,
|
||||
path: 'post',
|
||||
message: 'This post could not be found'
|
||||
}
|
||||
const persistentLinks = PersistentLinks(options)
|
||||
|
||||
export default {
|
||||
mixins: [persistentLinks],
|
||||
computed: {
|
||||
routes() {
|
||||
const { slug, id } = this.$route.params
|
||||
return [
|
||||
{
|
||||
name: this.$t('common.post', null, 1),
|
||||
path: `/post/${this.$route.params.slug}`,
|
||||
path: `/post/${id}/${slug}`,
|
||||
children: [
|
||||
{
|
||||
name: this.$t('common.comment', null, 2),
|
||||
path: `/post/${this.$route.params.slug}#comments`
|
||||
path: `/post/${id}/${slug}#comments`
|
||||
},
|
||||
{
|
||||
name: this.$t('common.letsTalk'),
|
||||
path: `/post/${this.$route.params.slug}#lets-talk`
|
||||
path: `/post/${id}/${slug}#lets-talk`
|
||||
},
|
||||
{
|
||||
name: this.$t('common.versus'),
|
||||
path: `/post/${this.$route.params.slug}#versus`
|
||||
path: `/post/${id}/${slug}#versus`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: this.$t('common.moreInfo'),
|
||||
path: `/post/${this.$route.params.slug}/more-info`
|
||||
path: `/post/${id}/${slug}/more-info`
|
||||
},
|
||||
{
|
||||
name: this.$t('common.takeAction'),
|
||||
path: `/post/${this.$route.params.slug}/take-action`
|
||||
path: `/post/${id}/${slug}/take-action`
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -263,7 +263,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-name-post-slug {
|
||||
.page-name-post-id-slug {
|
||||
.content-menu {
|
||||
float: right;
|
||||
margin-right: -$space-x-small;
|
||||
34
webapp/pages/profile/_id.vue
Normal file
34
webapp/pages/profile/_id.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<nuxt-child />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import PersistentLinks from '~/mixins/persistentLinks.js'
|
||||
|
||||
const options = {
|
||||
queryId: gql`
|
||||
query($idOrSlug: ID) {
|
||||
User(id: $idOrSlug) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`,
|
||||
querySlug: gql`
|
||||
query($idOrSlug: String) {
|
||||
User(slug: $idOrSlug) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`,
|
||||
message: 'This user could not be found',
|
||||
path: 'profile'
|
||||
}
|
||||
const persistentLinks = PersistentLinks(options)
|
||||
|
||||
export default {
|
||||
mixins: [persistentLinks]
|
||||
}
|
||||
</script>
|
||||
@ -423,7 +423,7 @@ export default {
|
||||
border: #fff 5px solid;
|
||||
}
|
||||
|
||||
.page-name-profile-slug {
|
||||
.page-name-profile-id-slug {
|
||||
.ds-flex-item:first-child .content-menu {
|
||||
position: absolute;
|
||||
top: $space-x-small;
|
||||
@ -1,8 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
layout: 'blank',
|
||||
asyncData({ error }) {
|
||||
error({ statusCode: 404, message: 'Profile slug missing' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<ds-card :header="$t('settings.security.name')">
|
||||
<hc-empty
|
||||
icon="tasks"
|
||||
message="Coming Soon…"
|
||||
/>
|
||||
<change-password />
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import ChangePassword from '~/components/ChangePassword.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmpty
|
||||
ChangePassword
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
1173
webapp/yarn.lock
1173
webapp/yarn.lock
File diff suppressed because it is too large
Load Diff
206
yarn.lock
206
yarn.lock
@ -1356,7 +1356,7 @@ chai@^4.1.2:
|
||||
pathval "^1.1.0"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1:
|
||||
chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@ -1673,6 +1673,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
cross-env@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2"
|
||||
integrity sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==
|
||||
dependencies:
|
||||
cross-spawn "^6.0.5"
|
||||
is-windows "^1.0.0"
|
||||
|
||||
cross-fetch@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723"
|
||||
@ -1681,7 +1689,7 @@ cross-fetch@2.2.2:
|
||||
node-fetch "2.1.2"
|
||||
whatwg-fetch "2.0.4"
|
||||
|
||||
cross-spawn@^6.0.0:
|
||||
cross-spawn@^6.0.0, 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==
|
||||
@ -1896,6 +1904,13 @@ 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"
|
||||
@ -2049,6 +2064,27 @@ 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"
|
||||
@ -2357,7 +2393,7 @@ fsevents@^1.0.0, fsevents@^1.2.7:
|
||||
nan "^2.9.2"
|
||||
node-pre-gyp "^0.10.0"
|
||||
|
||||
function-bind@^1.1.1:
|
||||
function-bind@^1.0.2, 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==
|
||||
@ -2499,6 +2535,11 @@ has-flag@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
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"
|
||||
@ -2535,7 +2576,7 @@ has-values@^1.0.0:
|
||||
is-number "^3.0.0"
|
||||
kind-of "^4.0.0"
|
||||
|
||||
has@^1.0.0:
|
||||
has@^1.0.0, has@^1.0.1, 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==
|
||||
@ -2567,6 +2608,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"
|
||||
@ -2701,6 +2747,11 @@ 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@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
|
||||
@ -2722,6 +2773,11 @@ 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"
|
||||
@ -2880,17 +2936,31 @@ 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"
|
||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||
|
||||
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"
|
||||
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
|
||||
|
||||
is-windows@^1.0.2:
|
||||
is-windows@^1.0.0, is-windows@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||
@ -3122,6 +3192,16 @@ listr@0.12.0:
|
||||
stream-to-observable "^0.1.0"
|
||||
strip-ansi "^3.0.1"
|
||||
|
||||
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"
|
||||
|
||||
lodash.memoize@~3.0.3:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
|
||||
@ -3197,6 +3277,11 @@ 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=
|
||||
|
||||
micromatch@^2.1.5:
|
||||
version "2.3.11"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
|
||||
@ -3455,6 +3540,16 @@ 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.0.0, normalize-path@^2.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"
|
||||
@ -3480,6 +3575,21 @@ npm-packlist@^1.1.6:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
||||
npm-run-all@^4.1.5:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
|
||||
integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
chalk "^2.4.1"
|
||||
cross-spawn "^6.0.5"
|
||||
memorystream "^0.3.1"
|
||||
minimatch "^3.0.4"
|
||||
pidtree "^0.3.0"
|
||||
read-pkg "^3.0.0"
|
||||
shell-quote "^1.6.1"
|
||||
string.prototype.padend "^3.0.0"
|
||||
|
||||
npm-run-path@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||
@ -3521,6 +3631,11 @@ 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"
|
||||
@ -3694,6 +3809,13 @@ 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"
|
||||
@ -3720,11 +3842,21 @@ performance-now@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
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=
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
@ -3848,6 +3980,15 @@ 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"
|
||||
@ -4009,7 +4150,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.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.8.1:
|
||||
resolve@^1.1.4, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
|
||||
integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
|
||||
@ -4078,6 +4219,11 @@ 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":
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
|
||||
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
|
||||
|
||||
semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
@ -4222,6 +4368,32 @@ source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.3:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
|
||||
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"
|
||||
@ -4353,6 +4525,15 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.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"
|
||||
@ -4381,6 +4562,11 @@ strip-ansi@^4.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^3.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-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
@ -4695,6 +4881,14 @@ uuid@^3.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user