mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-01-15 09:24:34 +00:00
Merge branch '1709-send-feedbacks-in-report-processes' of github.com:Human-Connection/Human-Connection into 1709-send-feedbacks-in-report-processes
This commit is contained in:
commit
346a1b0d3e
@ -18,7 +18,7 @@ before_script:
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml build # just tagging, just be quite fast
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml up -d
|
||||
- wait-on http://localhost:7474
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec neo4j db_setup
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec backend yarn run db:migrate init
|
||||
|
||||
script:
|
||||
- export CYPRESS_RETRIES=1
|
||||
|
||||
@ -64,7 +64,7 @@ Regular pair programming sessions
|
||||
* we team up and work on an issue together (often using Visual Studio live sharing sessions)
|
||||
|
||||
Open-Source Community Meeting
|
||||
* every Thursday 13:00
|
||||
* bi-weekly on Mondays 13:00 (when there is no sprint retrospective)
|
||||
* the link will be posted in the [discord chat](https://discord.gg/6ub73U3) and on the [Agile Ventures website](https://www.agileventures.org/events?utf8=%E2%9C%93&project_id=220&commit=Filter+by+Project)
|
||||
* all contributors welcome!
|
||||
|
||||
@ -99,3 +99,34 @@ We believe in open source contributions as a learning experience – everyone is
|
||||
We use pair programming sessions as a tool for knowledge sharing. We can learn a lot from each other and only by sharing what we know and overcoming challenges together can we grow as a team and truly own this project collectively.
|
||||
|
||||
As a volunteeer you have no commitment except your own self development and your awesomeness by contributing to this free and open-source software project. Cheers to you!
|
||||
|
||||
|
||||
## Open-Source Bounties
|
||||
|
||||
There are so many good reasons to contribute to Human Connection
|
||||
* You learn state-of-the-art technologies
|
||||
* You build your portfolio
|
||||
* You contribute to a good cause
|
||||
|
||||
Now there is one more good reason: You can receive a small fincancial
|
||||
compensation for your contribution! :tada:
|
||||
|
||||
### How it works
|
||||
|
||||
Before you can benefit from the Open-Source bounty program you **must get one
|
||||
pull request approved and merged for free**. You can choose something really
|
||||
quick and easy. What's important is starting a working relationship with the
|
||||
team, learning the workflow, and understanding this contribution guide. You can
|
||||
filter issues by 'good first issue', to get an idea where to start. Please join
|
||||
our our [community chat](https://human-connection.org/discord), too.
|
||||
|
||||
You can filter Github issues with label [bounty](https://github.com/Human-Connection/Human-Connection/issues?q=is%3Aopen+is%3Aissue+label%3Abounty). These issues should have a second label `€<amount>`
|
||||
which indicate their respective financial compensation in Euros.
|
||||
|
||||
You can bill us after your pull request got approved and merged into `master`.
|
||||
Payment methods are up to you: Bank transfer or PayPal is fine for us. Just send
|
||||
us your invoice as .pdf file attached to an E-Mail once you are done.
|
||||
|
||||
Our Open-Source bounty program is a work-in-progress. Based on our future
|
||||
experience we will make changes and improvements. So keep an eye on this
|
||||
contribution guide.
|
||||
|
||||
@ -52,6 +52,10 @@ Check out the [contribution guideline](./CONTRIBUTING.md), too!
|
||||
|
||||
[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/0)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/1)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/2)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/3)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/4)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/5)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/6)[](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/7)
|
||||
|
||||
## Open-Source Bounties
|
||||
|
||||
You can get a small financial compensation for your contribution :moneybag: See
|
||||
details in our [Contribution Guidelines](./CONTRIBUTING.md#open-source-bounties).
|
||||
|
||||
## Attributions
|
||||
|
||||
|
||||
@ -53,6 +53,27 @@ can issue GraphQL requests or access GraphQL Playground in the browser.
|
||||
|
||||

|
||||
|
||||
### Database Indices and Constraints
|
||||
|
||||
Database indices and constraints need to be created when the database and the
|
||||
backend is running:
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Docker" %}
|
||||
```bash
|
||||
docker-compose exec backend yarn run db:migrate init
|
||||
```
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Without Docker" %}
|
||||
```bash
|
||||
# in folder backend/
|
||||
# make sure your database is running on http://localhost:7474/browser/
|
||||
yarn run db:migrate init
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
|
||||
#### Seed Database
|
||||
|
||||
@ -73,7 +94,7 @@ $ docker-compose exec backend yarn run db:reset
|
||||
# you could also wipe out your neo4j database and delete all volumes with:
|
||||
$ docker-compose down -v
|
||||
# if container is not running, run this command to set up your database indeces and contstraints
|
||||
$ docker-compose run neo4j db_setup
|
||||
$ docker-compose run backend yarn run db:migrate init
|
||||
```
|
||||
{% endtab %}
|
||||
|
||||
@ -90,6 +111,38 @@ $ yarn run db:reset
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
### Data migrations
|
||||
|
||||
Although Neo4J is schema-less,you might find yourself in a situation in which
|
||||
you have to migrate your data e.g. because your data modeling has changed.
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Docker" %}
|
||||
Generate a data migration file:
|
||||
```bash
|
||||
$ docker-compose exec backend yarn run db:migrate:create your_data_migration
|
||||
# Edit the file in ./src/db/migrations/
|
||||
```
|
||||
|
||||
To run the migration:
|
||||
```bash
|
||||
$ docker-compose exec backend yarn run db:migrate up
|
||||
```
|
||||
{% endtab %}
|
||||
{% tab title="Without Docker" %}
|
||||
Generate a data migration file:
|
||||
```bash
|
||||
$ yarn run db:migrate:create your_data_migration
|
||||
# Edit the file in ./src/db/migrations/
|
||||
```
|
||||
|
||||
To run the migration:
|
||||
```bash
|
||||
$ yarn run db:migrate up
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
# Testing
|
||||
|
||||
**Beware**: We have no multiple database setup at the moment. We clean the
|
||||
|
||||
@ -4,14 +4,19 @@
|
||||
"description": "GraphQL Backend for Human Connection",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"build": "babel src/ -d dist/ --copy-files",
|
||||
"__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations",
|
||||
"prod:migrate": "migrate --migrations-dir ./dist/db/migrations --store ./dist/db/migrate/store.js",
|
||||
"start": "node dist/",
|
||||
"build": "babel src/ -d dist/ --copy-files",
|
||||
"dev": "nodemon --exec babel-node src/ -e js,gql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"test": "jest --forceExit --detectOpenHandles --runInBand",
|
||||
"db:reset": "babel-node src/seed/reset-db.js",
|
||||
"db:seed": "babel-node src/seed/seed-db.js"
|
||||
"db:clean": "babel-node src/db/clean.js",
|
||||
"db:reset": "yarn run db:clean",
|
||||
"db:seed": "babel-node src/db/seed.js",
|
||||
"db:migrate": "yarn run __migrate --store ./src/db/migrate/store.js",
|
||||
"db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.js --date-format 'yyyymmddHHmmss' create"
|
||||
},
|
||||
"author": "Human Connection gGmbH",
|
||||
"license": "MIT",
|
||||
@ -33,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^17.1.0",
|
||||
"@sentry/node": "^5.11.1",
|
||||
"@sentry/node": "^5.11.2",
|
||||
"apollo-cache-inmemory": "~1.6.5",
|
||||
"apollo-client": "~2.6.8",
|
||||
"apollo-link-context": "~1.0.19",
|
||||
@ -44,40 +49,41 @@
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~6.0.3",
|
||||
"cross-env": "~7.0.0",
|
||||
"date-fns": "2.9.0",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql": "^14.5.8",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-shield": "~7.0.7",
|
||||
"graphql-shield": "~7.0.9",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"helmet": "~3.21.2",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.6",
|
||||
"metascraper": "^5.10.5",
|
||||
"metascraper-audio": "^5.10.5",
|
||||
"metascraper-author": "^5.10.5",
|
||||
"metascraper": "^5.10.6",
|
||||
"metascraper-audio": "^5.10.6",
|
||||
"metascraper-author": "^5.10.6",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
"metascraper-date": "^5.10.5",
|
||||
"metascraper-description": "^5.10.5",
|
||||
"metascraper-image": "^5.10.5",
|
||||
"metascraper-lang": "^5.10.5",
|
||||
"metascraper-date": "^5.10.6",
|
||||
"metascraper-description": "^5.10.6",
|
||||
"metascraper-image": "^5.10.6",
|
||||
"metascraper-lang": "^5.10.6",
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.10.5",
|
||||
"metascraper-publisher": "^5.10.5",
|
||||
"metascraper-soundcloud": "^5.10.5",
|
||||
"metascraper-title": "^5.10.5",
|
||||
"metascraper-url": "^5.10.5",
|
||||
"metascraper-video": "^5.10.5",
|
||||
"metascraper-youtube": "^5.10.5",
|
||||
"metascraper-logo": "^5.10.6",
|
||||
"metascraper-publisher": "^5.10.6",
|
||||
"metascraper-soundcloud": "^5.10.6",
|
||||
"metascraper-title": "^5.10.6",
|
||||
"metascraper-url": "^5.10.6",
|
||||
"metascraper-video": "^5.10.6",
|
||||
"metascraper-youtube": "^5.10.6",
|
||||
"migrate": "^1.6.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"mustache": "^4.0.0",
|
||||
"neo4j-driver": "^4.0.1",
|
||||
@ -89,28 +95,28 @@
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.0",
|
||||
"sanitize-html": "~1.21.1",
|
||||
"slug": "~2.1.0",
|
||||
"slug": "~2.1.1",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~3.4.0",
|
||||
"validator": "^12.1.0",
|
||||
"validator": "^12.2.0",
|
||||
"wait-on": "~4.0.0",
|
||||
"xregexp": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.8.3",
|
||||
"@babel/core": "~7.8.3",
|
||||
"@babel/node": "~7.8.3",
|
||||
"@babel/cli": "~7.8.4",
|
||||
"@babel/core": "~7.8.4",
|
||||
"@babel/node": "~7.8.4",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
|
||||
"@babel/preset-env": "~7.8.3",
|
||||
"@babel/register": "~7.8.3",
|
||||
"@babel/preset-env": "~7.8.4",
|
||||
"@babel/register": "^7.8.3",
|
||||
"apollo-server-testing": "~2.9.16",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.3",
|
||||
"babel-jest": "~24.9.0",
|
||||
"babel-jest": "~25.1.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~6.0.5",
|
||||
"eslint": "~6.8.0",
|
||||
"eslint-config-prettier": "~6.9.0",
|
||||
"eslint-config-prettier": "~6.10.0",
|
||||
"eslint-config-standard": "~14.1.0",
|
||||
"eslint-plugin-import": "~2.20.0",
|
||||
"eslint-plugin-jest": "~23.6.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { handler } from './webfinger'
|
||||
import Factory from '../../seed/factories'
|
||||
import { getDriver } from '../../bootstrap/neo4j'
|
||||
import Factory from '../../factories'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
let resource, res, json, status, contentType
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { cleanDatabase } from './factories'
|
||||
import { cleanDatabase } from '../factories'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
throw new Error(`You cannot clean the database in production environment!`)
|
||||
97
backend/src/db/migrate/store.js
Normal file
97
backend/src/db/migrate/store.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
|
||||
class Store {
|
||||
async init(next) {
|
||||
const neode = getNeode()
|
||||
const { driver } = neode
|
||||
const session = driver.session()
|
||||
// eslint-disable-next-line no-console
|
||||
const writeTxResultPromise = session.writeTransaction(async txc => {
|
||||
await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices
|
||||
return Promise.all(
|
||||
[
|
||||
'CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"])',
|
||||
'CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"])',
|
||||
].map(statement => txc.run(statement)),
|
||||
)
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
await getNeode().schema.install()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Successfully created database indices and constraints!')
|
||||
next()
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error, null)
|
||||
} finally {
|
||||
session.close()
|
||||
driver.close()
|
||||
}
|
||||
}
|
||||
|
||||
async load(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async txc => {
|
||||
const result = await txc.run(
|
||||
'MATCH (migration:Migration) RETURN migration {.*} ORDER BY migration.timestamp DESC',
|
||||
)
|
||||
return result.records.map(r => r.get('migration'))
|
||||
})
|
||||
try {
|
||||
const migrations = await readTxResultPromise
|
||||
if (migrations.length <= 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
"No migrations found in database. If it's the first time you run migrations, then this is normal.",
|
||||
)
|
||||
return next(null, {})
|
||||
}
|
||||
const [{ title: lastRun }] = migrations
|
||||
next(null, { lastRun, migrations })
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
async save(set, next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const { migrations } = set
|
||||
const writeTxResultPromise = session.writeTransaction(txc => {
|
||||
return Promise.all(
|
||||
migrations.map(async migration => {
|
||||
const { title, description, timestamp } = migration
|
||||
const properties = { title, description, timestamp }
|
||||
const migrationResult = await txc.run(
|
||||
`
|
||||
MERGE (migration:Migration { title: $properties.title })
|
||||
ON MATCH SET
|
||||
migration += $properties
|
||||
ON CREATE SET
|
||||
migration += $properties,
|
||||
migration.migratedAt = toString(datetime())
|
||||
`,
|
||||
{ properties },
|
||||
)
|
||||
return migrationResult
|
||||
}),
|
||||
)
|
||||
})
|
||||
try {
|
||||
await writeTxResultPromise
|
||||
next()
|
||||
} catch (error) {
|
||||
console.log(error) // eslint-disable-line no-console
|
||||
next(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Store
|
||||
45
backend/src/db/migrate/template.js
Normal file
45
backend/src/db/migrate/template.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
export const description = ''
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(``)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(``)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
import { throwError, concat } from 'rxjs'
|
||||
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
|
||||
import { getDriver } from '../neo4j'
|
||||
import normalizeEmail from '../../schema/resolvers//helpers/normalizeEmail'
|
||||
|
||||
export const description = `
|
||||
This migration merges duplicate :User and :EmailAddress nodes. It became
|
||||
necessary after we implemented the email normalization but forgot to migrate
|
||||
the existing data. Some (40) users decided to just register with a new account
|
||||
but the same email address. On signup our backend would normalize the email,
|
||||
which is good, but would also keep the existing unnormalized email address.
|
||||
|
||||
This led to about 40 duplicate user and email address nodes in our database.
|
||||
`
|
||||
export function up(next) {
|
||||
const driver = getDriver()
|
||||
const rxSession = driver.rxSession()
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
flatMap(txc =>
|
||||
concat(
|
||||
txc
|
||||
.run('MATCH (email:EmailAddress) RETURN email {.email}')
|
||||
.records()
|
||||
.pipe(
|
||||
map(record => {
|
||||
const { email } = record.get('email')
|
||||
const normalizedEmail = normalizeEmail(email)
|
||||
return { email, normalizedEmail }
|
||||
}),
|
||||
filter(({ email, normalizedEmail }) => email !== normalizedEmail),
|
||||
mergeMap(({ email, normalizedEmail }) => {
|
||||
return txc
|
||||
.run(
|
||||
`
|
||||
MATCH (oldUser:User)-[:PRIMARY_EMAIL]->(oldEmail:EmailAddress {email: $email}), (oldUser)-[previousRelationship]-(oldEmail)
|
||||
MATCH (user:User)-[:PRIMARY_EMAIL]->(email:EmailAddress {email: $normalizedEmail})
|
||||
DELETE previousRelationship
|
||||
WITH oldUser, oldEmail, user, email
|
||||
CALL apoc.refactor.mergeNodes([user, oldUser], { properties: 'discard', mergeRels: true }) YIELD node as mergedUser
|
||||
CALL apoc.refactor.mergeNodes([email, oldEmail], { properties: 'discard', mergeRels: true }) YIELD node as mergedEmail
|
||||
RETURN user {.*}, email {.*}
|
||||
`,
|
||||
{ email, normalizedEmail },
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map(r => ({
|
||||
oldEmail: email,
|
||||
email: r.get('email'),
|
||||
user: r.get('user'),
|
||||
})),
|
||||
)
|
||||
}),
|
||||
),
|
||||
txc.commit(),
|
||||
).pipe(catchError(err => txc.rollback().pipe(throwError(err)))),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: ({ user, email, oldUser, oldEmail }) =>
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`
|
||||
Merged:
|
||||
=============================
|
||||
userId: ${user.id}
|
||||
email: ${oldEmail} => ${email.email}
|
||||
=============================
|
||||
`),
|
||||
complete: () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Merging of duplicate users completed')
|
||||
next()
|
||||
},
|
||||
error: error => {
|
||||
next(new Error(error), null)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function down(next) {
|
||||
next(new Error('Irreversible migration'))
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
import { throwError, concat } from 'rxjs'
|
||||
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
|
||||
import { getDriver } from '../neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration merges duplicate :Location nodes. It became
|
||||
necessary after we realized that we had not set up constraints for Location.id in production.
|
||||
`
|
||||
export function up(next) {
|
||||
const driver = getDriver()
|
||||
const rxSession = driver.rxSession()
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
flatMap(transaction =>
|
||||
concat(
|
||||
transaction
|
||||
.run(
|
||||
`
|
||||
MATCH (location:Location)
|
||||
RETURN location {.id}
|
||||
`,
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map(record => {
|
||||
const { id: locationId } = record.get('location')
|
||||
return { locationId }
|
||||
}),
|
||||
mergeMap(({ locationId }) => {
|
||||
return transaction
|
||||
.run(
|
||||
`
|
||||
MATCH(location:Location {id: $locationId}), (location2:Location {id: $locationId})
|
||||
WHERE location.id = location2.id AND id(location) < id(location2)
|
||||
CALL apoc.refactor.mergeNodes([location, location2], { properties: 'combine', mergeRels: true }) YIELD node as updatedLocation
|
||||
RETURN location {.*},updatedLocation {.*}
|
||||
`,
|
||||
{ locationId },
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map(record => ({
|
||||
location: record.get('location'),
|
||||
updatedLocation: record.get('updatedLocation'),
|
||||
})),
|
||||
)
|
||||
}),
|
||||
),
|
||||
transaction.commit(),
|
||||
).pipe(catchError(error => transaction.rollback().pipe(throwError(error)))),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: ({ updatedLocation, location }) =>
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`
|
||||
Merged:
|
||||
=============================
|
||||
locationId: ${location.id}
|
||||
updatedLocation: ${location.id} => ${updatedLocation.id}
|
||||
=============================
|
||||
`),
|
||||
complete: () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Merging of duplicate locations completed')
|
||||
next()
|
||||
},
|
||||
error: error => {
|
||||
next(new Error(error), null)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function down(next) {
|
||||
next(new Error('Irreversible migration'))
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
export const description = `
|
||||
This migration creates a MUTED relationship between two edges(:User) that have a pre-existing BLOCKED relationship.
|
||||
It also sets the createdAt date for the BLOCKED relationship to the datetime the migration was run. This became
|
||||
necessary after we redefined what it means to block someone, and what it means to mute them. Muting is about filtering
|
||||
another user's content, whereas blocking means preventing that user from interacting with you/your contributions.
|
||||
A blocked user will still be able to see your contributions, but will not be able to interact with them and vice versa.
|
||||
`
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
try {
|
||||
await transaction.run(
|
||||
`
|
||||
MATCH (blocker:User)-[blocked:BLOCKED]->(blockee:User)
|
||||
MERGE (blocker)-[muted:MUTED]->(blockee)
|
||||
SET muted.createdAt = toString(datetime()), blocked.createdAt = toString(datetime())
|
||||
`,
|
||||
)
|
||||
await transaction.commit()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
try {
|
||||
// Rollback your migration here.
|
||||
next()
|
||||
} catch (err) {
|
||||
next(err)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,8 @@ import faker from 'faker'
|
||||
import sample from 'lodash/sample'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory from './factories'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import Factory from '../factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { gql } from '../helpers/jest'
|
||||
|
||||
const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
|
||||
const factories = {
|
||||
Badge: require('./badges.js').default,
|
||||
@ -21,11 +21,15 @@ export default function create() {
|
||||
categoryIds: [],
|
||||
imageBlurred: false,
|
||||
imageAspectRatio: 1.333,
|
||||
pinned: null,
|
||||
}
|
||||
args = {
|
||||
...defaults,
|
||||
...args,
|
||||
}
|
||||
// Convert false to null
|
||||
args.pinned = args.pinned || null
|
||||
|
||||
args.slug = args.slug || slugify(args.title, { lower: true })
|
||||
args.contentExcerpt = args.contentExcerpt || args.content
|
||||
|
||||
@ -50,9 +54,33 @@ export default function create() {
|
||||
if (author && authorId) throw new Error('You provided both author and authorId')
|
||||
if (authorId) author = await neodeInstance.find('User', authorId)
|
||||
author = author || (await factoryInstance.create('User'))
|
||||
|
||||
const post = await neodeInstance.create('Post', args)
|
||||
|
||||
const { commentContent } = args
|
||||
let comment
|
||||
delete args.commentContent
|
||||
if (commentContent)
|
||||
comment = await factoryInstance.create('Comment', {
|
||||
contentExcerpt: commentContent,
|
||||
post,
|
||||
author,
|
||||
})
|
||||
|
||||
await post.relateTo(author, 'author')
|
||||
if (comment) await post.relateTo(comment, 'comments')
|
||||
|
||||
if (args.pinned) {
|
||||
args.pinnedAt = args.pinnedAt || new Date().toISOString()
|
||||
if (!args.pinnedBy) {
|
||||
const admin = await factoryInstance.create('User', {
|
||||
role: 'admin',
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
await admin.relateTo(post, 'pinned')
|
||||
args.pinnedBy = admin
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(categories.map(c => c.relateTo(post, 'post')))
|
||||
await Promise.all(tags.map(t => t.relateTo(post, 'post')))
|
||||
return post
|
||||
@ -1,6 +1,6 @@
|
||||
import faker from 'faker'
|
||||
import uuid from 'uuid/v4'
|
||||
import encryptPassword from '../../helpers/encryptPassword'
|
||||
import encryptPassword from '../helpers/encryptPassword'
|
||||
import slugify from 'slug'
|
||||
|
||||
export default function create() {
|
||||
@ -1,5 +0,0 @@
|
||||
//* This is a fake ES2015 template string, just to benefit of syntax
|
||||
// highlighting of `gql` template strings in certain editors.
|
||||
export function gql(strings) {
|
||||
return strings.join('')
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory from '../seed/factories/index'
|
||||
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||
import Factory from '../factories/index'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
import decode from './decode'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let server
|
||||
|
||||
@ -30,7 +30,7 @@ const postAuthorOfComment = async (commentId, { context }) => {
|
||||
try {
|
||||
postAuthorId = await session.readTransaction(transaction => {
|
||||
return transaction.run(
|
||||
`
|
||||
`
|
||||
MATCH (author:User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId })
|
||||
RETURN author { .id } as authorId
|
||||
`,
|
||||
@ -54,7 +54,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||
MATCH (user: User)
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)<-[:BLOCKED]-(author)
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
`
|
||||
break
|
||||
@ -64,8 +64,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User)
|
||||
MATCH (user: User)
|
||||
WHERE user.id in $idsOfUsers
|
||||
AND NOT (user)<-[:BLOCKED]-(author)
|
||||
AND NOT (user)<-[:BLOCKED]-(postAuthor)
|
||||
AND NOT (user)-[:BLOCKED]-(author)
|
||||
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||
`
|
||||
break
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let server, query, mutate, notifiedUser, authenticatedUser
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { gql } from '../helpers/jest'
|
||||
import Factory from '../seed/factories'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import Factory from '../factories'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||
import { getNeode } from '../bootstrap/neo4j'
|
||||
import { getNeode } from '../db/neo4j'
|
||||
import CONFIG from '../config'
|
||||
|
||||
const debug = !!CONFIG.DEBUG
|
||||
@ -102,6 +102,7 @@ export default shield(
|
||||
PostsEmotionsCountByEmotion: allow,
|
||||
PostsEmotionsByCurrentUser: isAuthenticated,
|
||||
mutedUsers: isAuthenticated,
|
||||
blockedUsers: isAuthenticated,
|
||||
notifications: isAuthenticated,
|
||||
Donations: isAuthenticated,
|
||||
},
|
||||
@ -139,6 +140,8 @@ export default shield(
|
||||
RemovePostEmotions: isAuthenticated,
|
||||
muteUser: isAuthenticated,
|
||||
unmuteUser: isAuthenticated,
|
||||
blockUser: isAuthenticated,
|
||||
unblockUser: isAuthenticated,
|
||||
markAsRead: isAuthenticated,
|
||||
AddEmailAddress: isAuthenticated,
|
||||
VerifyEmailAddress: isAuthenticated,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../server'
|
||||
import Factory from '../seed/factories'
|
||||
import Factory from '../factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import { getDriver, getNeode } from '../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../db/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const instance = getNeode()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../seed/factories'
|
||||
import Factory from '../factories'
|
||||
import { gql } from '../helpers/jest'
|
||||
import { getNeode, getDriver } from '../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../db/neo4j'
|
||||
import createServer from '../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { gql } from '../../helpers/jest'
|
||||
import Factory from '../../seed/factories'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import Factory from '../../factories'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ function clean(dirty) {
|
||||
'hr',
|
||||
'b',
|
||||
'i',
|
||||
'u',
|
||||
'em',
|
||||
'strong',
|
||||
'a',
|
||||
|
||||
@ -3,7 +3,7 @@ import uuid from 'uuid/v4'
|
||||
export default {
|
||||
id: { type: 'string', primary: true, default: uuid },
|
||||
name: { type: 'string', required: true, default: false },
|
||||
slug: { type: 'string' },
|
||||
slug: { type: 'string', unique: 'true' },
|
||||
icon: { type: 'string', required: true, default: false },
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
updatedAt: {
|
||||
|
||||
5
backend/src/models/Migration.js
Normal file
5
backend/src/models/Migration.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: { type: 'string', primary: true, token: true },
|
||||
description: { type: 'string' },
|
||||
migratedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
}
|
||||
@ -11,7 +11,7 @@ export default {
|
||||
direction: 'in',
|
||||
},
|
||||
title: { type: 'string', disallow: [null], min: 3 },
|
||||
slug: { type: 'string', allow: [null] },
|
||||
slug: { type: 'string', allow: [null], unique: 'true' },
|
||||
content: { type: 'string', disallow: [null], min: 3 },
|
||||
contentExcerpt: { type: 'string', allow: [null] },
|
||||
image: { type: 'string', allow: [null] },
|
||||
@ -52,4 +52,16 @@ export default {
|
||||
language: { type: 'string', allow: [null] },
|
||||
imageBlurred: { type: 'boolean', default: false },
|
||||
imageAspectRatio: { type: 'float', default: 1.0 },
|
||||
comments: {
|
||||
type: 'relationship',
|
||||
relationship: 'COMMENTS',
|
||||
target: 'Comment',
|
||||
direction: 'in',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
updatedAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
pinned: { type: 'boolean', default: null, valid: [null, true] },
|
||||
pinnedAt: { type: 'string', isoDate: true },
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
email: { type: 'string', primary: true, lowercase: true, email: true },
|
||||
email: { type: 'string', lowercase: true, email: true },
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
nonce: { type: 'string', token: true },
|
||||
belongsTo: {
|
||||
|
||||
@ -4,7 +4,7 @@ export default {
|
||||
id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests
|
||||
actorId: { type: 'string', allow: [null] },
|
||||
name: { type: 'string', disallow: [null], min: 3 },
|
||||
slug: { type: 'string', regex: /^[a-z0-9_-]+$/, lowercase: true },
|
||||
slug: { type: 'string', unique: 'true', regex: /^[a-z0-9_-]+$/, lowercase: true },
|
||||
encryptedPassword: 'string',
|
||||
avatar: { type: 'string', allow: [null] },
|
||||
coverImg: { type: 'string', allow: [null] },
|
||||
@ -77,12 +77,18 @@ export default {
|
||||
relationship: 'BLOCKED',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
muted: {
|
||||
type: 'relationship',
|
||||
relationship: 'MUTED',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
properties: {
|
||||
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
type: 'relationship',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { getNeode } from '../bootstrap/neo4j'
|
||||
import Factory from '../factories'
|
||||
import { getNeode } from '../db/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
|
||||
@ -13,4 +13,5 @@ export default {
|
||||
Location: require('./Location.js').default,
|
||||
Donations: require('./Donations.js').default,
|
||||
Report: require('./Report.js').default,
|
||||
Migration: require('./Migration.js').default,
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let mutate, query, authenticatedUser, variables
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import Factory from '../../factories'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { gql } from '../../helpers/jest'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver } from '../../bootstrap/neo4j'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createPasswordReset from './helpers/createPasswordReset'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const driver = getDriver()
|
||||
@ -682,58 +682,62 @@ describe('UpdatePost', () => {
|
||||
})
|
||||
|
||||
describe('PostOrdering', () => {
|
||||
let pinnedPost, admin
|
||||
beforeEach(async () => {
|
||||
;[pinnedPost] = await Promise.all([
|
||||
neode.create('Post', {
|
||||
id: 'im-a-pinned-post',
|
||||
pinned: true,
|
||||
}),
|
||||
neode.create('Post', {
|
||||
id: 'i-was-created-after-pinned-post',
|
||||
createdAt: '2019-10-22T17:26:29.070Z', // this should always be 3rd
|
||||
}),
|
||||
])
|
||||
admin = await user.update({
|
||||
role: 'admin',
|
||||
name: 'Admin',
|
||||
updatedAt: new Date().toISOString(),
|
||||
await factory.create('Post', {
|
||||
id: 'im-a-pinned-post',
|
||||
createdAt: '2019-11-22T17:26:29.070Z',
|
||||
pinned: true,
|
||||
})
|
||||
await factory.create('Post', {
|
||||
id: 'i-was-created-before-pinned-post',
|
||||
// fairly old, so this should be 3rd
|
||||
createdAt: '2019-10-22T17:26:29.070Z',
|
||||
})
|
||||
await admin.relateTo(pinnedPost, 'pinned')
|
||||
})
|
||||
|
||||
it('pinned post appear first even when created before other posts', async () => {
|
||||
const postOrderingQuery = gql`
|
||||
query($orderBy: [_PostOrdering]) {
|
||||
Post(orderBy: $orderBy) {
|
||||
id
|
||||
pinnedAt
|
||||
describe('order by `pinned_asc` and `createdAt_desc`', () => {
|
||||
beforeEach(() => {
|
||||
// this is the ordering in the frontend
|
||||
variables = { orderBy: ['pinned_asc', 'createdAt_desc'] }
|
||||
})
|
||||
|
||||
it('pinned post appear first even when created before other posts', async () => {
|
||||
const postOrderingQuery = gql`
|
||||
query($orderBy: [_PostOrdering]) {
|
||||
Post(orderBy: $orderBy) {
|
||||
id
|
||||
pinned
|
||||
createdAt
|
||||
pinnedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const expected = {
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
id: 'im-a-pinned-post',
|
||||
pinnedAt: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: 'p9876',
|
||||
pinnedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'i-was-created-after-pinned-post',
|
||||
pinnedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
}
|
||||
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
|
||||
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
|
||||
expected,
|
||||
)
|
||||
`
|
||||
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject({
|
||||
data: {
|
||||
Post: [
|
||||
{
|
||||
id: 'im-a-pinned-post',
|
||||
pinned: true,
|
||||
createdAt: '2019-11-22T17:26:29.070Z',
|
||||
pinnedAt: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: 'p9876',
|
||||
pinned: null,
|
||||
createdAt: expect.any(String),
|
||||
pinnedAt: null,
|
||||
},
|
||||
{
|
||||
id: 'i-was-created-before-pinned-post',
|
||||
pinned: null,
|
||||
createdAt: '2019-10-22T17:26:29.070Z',
|
||||
pinnedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { UserInputError } from 'apollo-server'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
import fileUpload from './fileUpload'
|
||||
import encryptPassword from '../../helpers/encryptPassword'
|
||||
import generateNonce from './helpers/generateNonce'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../.././server'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getDriver, getNeode } from '../../bootstrap/neo4j'
|
||||
import { getDriver, getNeode } from '../../db/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const instance = getNeode()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
import { UserInputError } from 'apollo-server'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
@ -20,7 +20,7 @@ export default {
|
||||
AND NOT (
|
||||
author.deleted = true OR author.disabled = true
|
||||
OR resource.deleted = true OR resource.disabled = true
|
||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(author)
|
||||
OR (:User {id: $thisUserId})-[:MUTED]->(author)
|
||||
)
|
||||
WITH resource, author,
|
||||
[(resource)<-[:COMMENTS]-(comment:Comment) | comment] as comments,
|
||||
@ -40,8 +40,7 @@ export default {
|
||||
YIELD node as resource, score
|
||||
MATCH (resource)
|
||||
WHERE score >= 0.5
|
||||
AND NOT (resource.deleted = true OR resource.disabled = true
|
||||
OR (:User { id: $thisUserId })-[:BLOCKED]-(resource))
|
||||
AND NOT (resource.deleted = true OR resource.disabled = true)
|
||||
RETURN resource {.*, __typename: labels(resource)[0]}
|
||||
LIMIT $limit
|
||||
`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let mutate, query, authenticatedUser, variables
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
import Resolver from './helpers/Resolver'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../server'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
|
||||
let query, authenticatedUser
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import encode from '../../jwt/encode'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { AuthenticationError } from 'apollo-server'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
import normalizeEmail from './helpers/normalizeEmail'
|
||||
import log from './helpers/databaseLogger'
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from './../../config'
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer, { context } from '../../server'
|
||||
import encode from '../../jwt/encode'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
|
||||
const factory = Factory()
|
||||
const neode = getNeode()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import fileUpload from './fileUpload'
|
||||
import { getNeode } from '../../bootstrap/neo4j'
|
||||
import { getNeode } from '../../db/neo4j'
|
||||
import { UserInputError, ForbiddenError } from 'apollo-server'
|
||||
import Resolver from './helpers/Resolver'
|
||||
import log from './helpers/databaseLogger'
|
||||
@ -23,6 +23,21 @@ export const getMutedUsers = async context => {
|
||||
return mutedUsers
|
||||
}
|
||||
|
||||
export const getBlockedUsers = async context => {
|
||||
const { neode } = context
|
||||
const userModel = neode.model('User')
|
||||
let blockedUsers = neode
|
||||
.query()
|
||||
.match('user', userModel)
|
||||
.where('user.id', context.user.id)
|
||||
.relationship(userModel.relationships().get('blocked'))
|
||||
.to('blocked', userModel)
|
||||
.return('blocked')
|
||||
blockedUsers = await blockedUsers.execute()
|
||||
blockedUsers = blockedUsers.records.map(r => r.get('blocked').properties)
|
||||
return blockedUsers
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
mutedUsers: async (object, args, context, resolveInfo) => {
|
||||
@ -32,6 +47,13 @@ export default {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
blockedUsers: async (object, args, context, resolveInfo) => {
|
||||
try {
|
||||
return getBlockedUsers(context)
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
User: async (object, args, context, resolveInfo) => {
|
||||
const { email } = args
|
||||
if (email) {
|
||||
@ -86,7 +108,7 @@ export default {
|
||||
const unmutedUser = await neode.find('User', params.id)
|
||||
return unmutedUser.toJson()
|
||||
},
|
||||
block: async (object, args, context, resolveInfo) => {
|
||||
blockUser: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await neode.cypher(
|
||||
@ -103,7 +125,7 @@ export default {
|
||||
await user.relateTo(blockedUser, 'blocked')
|
||||
return blockedUser.toJson()
|
||||
},
|
||||
unblock: async (object, args, context, resolveInfo) => {
|
||||
unblockUser: async (object, args, context, resolveInfo) => {
|
||||
const { user: currentUser } = context
|
||||
if (currentUser.id === args.id) return null
|
||||
await neode.cypher(
|
||||
@ -229,7 +251,7 @@ export default {
|
||||
boolean: {
|
||||
followedByCurrentUser:
|
||||
'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isBlocked:
|
||||
blocked:
|
||||
'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
isMuted:
|
||||
'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Factory from '../../seed/factories'
|
||||
import Factory from '../../factories'
|
||||
import { gql } from '../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import Factory from '../../../seed/factories'
|
||||
import { getNeode, getDriver } from '../../../bootstrap/neo4j'
|
||||
import Factory from '../../../factories'
|
||||
import { getNeode, getDriver } from '../../../db/neo4j'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../../server'
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '../../../server'
|
||||
import Factory from '../../../seed/factories'
|
||||
import Factory from '../../../factories'
|
||||
import { gql } from '../../../helpers/jest'
|
||||
import { getNeode, getDriver } from '../../../bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from '../../../db/neo4j'
|
||||
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
|
||||
@ -68,10 +68,11 @@ type User {
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
isBlocked: Boolean! @cypher(
|
||||
|
||||
blocked: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(user) >= 1
|
||||
"""
|
||||
)
|
||||
|
||||
@ -207,6 +208,6 @@ type Mutation {
|
||||
|
||||
muteUser(id: ID!): User
|
||||
unmuteUser(id: ID!): User
|
||||
block(id: ID!): User
|
||||
unblock(id: ID!): User
|
||||
blockUser(id: ID!): User
|
||||
unblockUser(id: ID!): User
|
||||
}
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
const _ = require('lodash')
|
||||
const faker = require('faker')
|
||||
const unsplashTopics = [
|
||||
'love',
|
||||
'family',
|
||||
'spring',
|
||||
'business',
|
||||
'nature',
|
||||
'travel',
|
||||
'happy',
|
||||
'landscape',
|
||||
'health',
|
||||
'friends',
|
||||
'computer',
|
||||
'autumn',
|
||||
'space',
|
||||
'animal',
|
||||
'smile',
|
||||
'face',
|
||||
'people',
|
||||
'portrait',
|
||||
'amazing',
|
||||
]
|
||||
let unsplashTopicsTmp = []
|
||||
|
||||
const ngoLogos = [
|
||||
'http://www.fetchlogos.com/wp-content/uploads/2015/11/Girl-Scouts-Of-The-Usa-Logo.jpg',
|
||||
'http://logos.textgiraffe.com/logos/logo-name/Ngo-designstyle-friday-m.png',
|
||||
'http://seeklogo.com/images/N/ngo-logo-BD53A3E024-seeklogo.com.png',
|
||||
'https://dcassetcdn.com/design_img/10133/25833/25833_303600_10133_image.jpg',
|
||||
'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/20.jpg',
|
||||
'https://cdn.tutsplus.com/vector/uploads/legacy/articles/08bad_ngologos/33.jpg',
|
||||
null,
|
||||
]
|
||||
|
||||
const difficulties = ['easy', 'medium', 'hard']
|
||||
|
||||
export default {
|
||||
randomItem: (items, filter) => {
|
||||
const ids = filter
|
||||
? Object.keys(items).filter(id => {
|
||||
return filter(items[id])
|
||||
})
|
||||
: _.keys(items)
|
||||
const randomIds = _.shuffle(ids)
|
||||
return items[randomIds.pop()]
|
||||
},
|
||||
randomItems: (items, key = 'id', min = 1, max = 1) => {
|
||||
const randomIds = _.shuffle(_.keys(items))
|
||||
const res = []
|
||||
|
||||
const count = _.random(min, max)
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let r = items[randomIds.pop()][key]
|
||||
if (key === 'id') {
|
||||
r = r.toString()
|
||||
}
|
||||
res.push(r)
|
||||
}
|
||||
return res
|
||||
},
|
||||
random: items => {
|
||||
return _.shuffle(items).pop()
|
||||
},
|
||||
randomDifficulty: () => {
|
||||
return _.shuffle(difficulties).pop()
|
||||
},
|
||||
randomLogo: () => {
|
||||
return _.shuffle(ngoLogos).pop()
|
||||
},
|
||||
randomUnsplashUrl: () => {
|
||||
if (Math.random() < 0.6) {
|
||||
// do not attach images in 60 percent of the cases (faster seeding)
|
||||
return
|
||||
}
|
||||
if (unsplashTopicsTmp.length < 2) {
|
||||
unsplashTopicsTmp = _.shuffle(unsplashTopics)
|
||||
}
|
||||
return (
|
||||
'https://source.unsplash.com/daily?' + unsplashTopicsTmp.pop() + ',' + unsplashTopicsTmp.pop()
|
||||
)
|
||||
},
|
||||
randomCategories: (seederstore, allowEmpty = false) => {
|
||||
let count = Math.round(Math.random() * 3)
|
||||
if (allowEmpty === false && count === 0) {
|
||||
count = 1
|
||||
}
|
||||
const categorieIds = _.shuffle(_.keys(seederstore.categories))
|
||||
const ids = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
ids.push(categorieIds.pop())
|
||||
}
|
||||
return ids
|
||||
},
|
||||
randomAddresses: () => {
|
||||
const count = Math.round(Math.random() * 3)
|
||||
const addresses = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
addresses.push({
|
||||
city: faker.address.city(),
|
||||
zipCode: faker.address.zipCode(),
|
||||
street: faker.address.streetAddress(),
|
||||
country: faker.address.countryCode(),
|
||||
lat: 54.032726 - Math.random() * 10,
|
||||
lng: 6.558838 + Math.random() * 10,
|
||||
})
|
||||
}
|
||||
return addresses
|
||||
},
|
||||
/**
|
||||
* Get array of ids from the given seederstore items after mapping them by the key in the values
|
||||
*
|
||||
* @param items items from the seederstore
|
||||
* @param values values for which you need the ids
|
||||
* @param key the field key that is represented in the values (slug, name, etc.)
|
||||
*/
|
||||
mapIdsByKey: (items, values, key) => {
|
||||
const res = []
|
||||
values.forEach(value => {
|
||||
res.push(_.find(items, [key, value]).id.toString())
|
||||
})
|
||||
return res
|
||||
},
|
||||
genInviteCode: () => {
|
||||
const chars = '23456789abcdefghkmnpqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ'
|
||||
let code = ''
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const n = _.random(0, chars.length - 1)
|
||||
code += chars.substr(n, 1)
|
||||
}
|
||||
return code
|
||||
},
|
||||
}
|
||||
@ -3,7 +3,7 @@ import helmet from 'helmet'
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import CONFIG from './config'
|
||||
import middleware from './middleware'
|
||||
import { getNeode, getDriver } from './bootstrap/neo4j'
|
||||
import { getNeode, getDriver } from './db/neo4j'
|
||||
import decode from './jwt/decode'
|
||||
import schema from './schema'
|
||||
import webfinger from './activitypub/routes/webfinger'
|
||||
|
||||
@ -3,7 +3,7 @@ import { Given, When, Then, AfterAll } from 'cucumber'
|
||||
import { expect } from 'chai'
|
||||
// import { client } from '../../../src/activitypub/apollo-client'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import Factory from '../../../src/seed/factories'
|
||||
import Factory from '../../../src/factories'
|
||||
const debug = require('debug')('ea:test:steps')
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -249,10 +249,12 @@ Shows automatically related actions for existing post.
|
||||
|
||||
### Administration
|
||||
|
||||
[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/administration)
|
||||
|
||||
* Provide Admin-Interface to send Users Invite Code
|
||||
* Static Pages for Data Privacy Statement ...
|
||||
* Create, edit and delete Announcements
|
||||
* Show Announcements on top of User Interface
|
||||
* Pin a post to inform users
|
||||
|
||||
### Invitation
|
||||
|
||||
|
||||
36
cypress/integration/administration/PinPost.feature
Normal file
36
cypress/integration/administration/PinPost.feature
Normal file
@ -0,0 +1,36 @@
|
||||
Feature: Pin a post
|
||||
As an admin
|
||||
I want to pin a post so that it always appears at the top
|
||||
In order to make sure all network users read it - e.g. notify people about security incidents, maintenance downtimes
|
||||
|
||||
|
||||
Background:
|
||||
Given we have the following posts in our database:
|
||||
| id | title | pinned | createdAt |
|
||||
| p1 | Some other post | | 2020-01-21 |
|
||||
| p2 | Houston we have a problem | x | 2020-01-20 |
|
||||
| p3 | Yet another post | | 2020-01-19 |
|
||||
|
||||
Scenario: Pinned post always appears on the top of the newsfeed
|
||||
Given I am logged in with a "user" role
|
||||
Then the first post on the landing page has the title:
|
||||
"""
|
||||
Houston we have a problem
|
||||
"""
|
||||
And the post with title "Houston we have a problem" has a ribbon for pinned posts
|
||||
|
||||
Scenario: Ordinary users cannot pin a post
|
||||
Given I am logged in with a "user" role
|
||||
When I open the content menu of post "Yet another post"
|
||||
Then there is no button to pin a post
|
||||
|
||||
Scenario: Admins are allowed to pin a post
|
||||
Given I am logged in with a "admin" role
|
||||
And I open the content menu of post "Yet another post"
|
||||
When I click on 'Pin post'
|
||||
Then I see a toaster with "Post pinned successfully"
|
||||
And the first post on the landing page has the title:
|
||||
"""
|
||||
Yet another post
|
||||
"""
|
||||
And the post with title "Yet another post" has a ribbon for pinned posts
|
||||
@ -17,8 +17,13 @@ Then("I click on the {string} button", text => {
|
||||
.click();
|
||||
});
|
||||
|
||||
Then("I click on the reply button", () => {
|
||||
cy.get(".reply-button")
|
||||
.click();
|
||||
});
|
||||
|
||||
Then("my comment should be successfully created", () => {
|
||||
cy.get(".iziToast-message").contains("Comment Submitted");
|
||||
cy.get(".iziToast-message").contains("Comment submitted!");
|
||||
});
|
||||
|
||||
Then("I should see my comment", () => {
|
||||
@ -44,3 +49,37 @@ Then("I should see an abreviated version of my comment", () => {
|
||||
Then("the editor should be cleared", () => {
|
||||
cy.get(".ProseMirror p").should("have.class", "is-empty");
|
||||
});
|
||||
|
||||
Then("it should create a mention in the CommentForm", () => {
|
||||
cy.get(".ProseMirror a")
|
||||
.should('have.class', 'mention')
|
||||
.should('contain', '@peter-pan')
|
||||
})
|
||||
|
||||
When("I open the content menu of post {string}", (title)=> {
|
||||
cy.contains('.post-card', title)
|
||||
.find('.content-menu .base-button')
|
||||
.click()
|
||||
})
|
||||
|
||||
When("I click on 'Pin post'", (string)=> {
|
||||
cy.get("a.ds-menu-item-link").contains("Pin post")
|
||||
.click()
|
||||
})
|
||||
|
||||
Then("there is no button to pin a post", () => {
|
||||
cy.get("a.ds-menu-item-link")
|
||||
.should('contain', "Report Post") // sanity check
|
||||
.should('not.contain', "Pin post")
|
||||
})
|
||||
|
||||
And("the post with title {string} has a ribbon for pinned posts", (title) => {
|
||||
cy.get("article.post-card").contains(title)
|
||||
.parent()
|
||||
.find("div.ribbon.ribbon--pinned")
|
||||
.should("contain", "Announcement")
|
||||
})
|
||||
|
||||
Then("I see a toaster with {string}", (title) => {
|
||||
cy.get(".iziToast-message").should("contain", title);
|
||||
})
|
||||
|
||||
@ -170,5 +170,4 @@ When("they have a post someone has reported", () => {
|
||||
authorId: 'annnoying-user',
|
||||
title,
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
@ -11,15 +11,24 @@ Then("I should have one item in the select dropdown", () => {
|
||||
});
|
||||
});
|
||||
|
||||
Then("the search has no results", () => {
|
||||
Then("the search should not contain posts by the annoying user", () => {
|
||||
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||
expect($li).to.have.length(1);
|
||||
});
|
||||
cy.get(".ds-select-dropdown").should("contain", 'Nothing found');
|
||||
})
|
||||
cy.get(".ds-select-dropdown")
|
||||
.should("not.have.class", '.search-post')
|
||||
.should("not.contain", 'Spam')
|
||||
});
|
||||
|
||||
Then("the search should contain the annoying user", () => {
|
||||
cy.get(".searchable-input .ds-select-dropdown").should($li => {
|
||||
expect($li).to.have.length(1);
|
||||
})
|
||||
cy.get(".ds-select-dropdown .user-teaser .slug").should("contain", '@spammy-spammer');
|
||||
cy.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type("{esc}");
|
||||
});
|
||||
})
|
||||
|
||||
Then("I should see the following posts in the select dropdown:", table => {
|
||||
table.hashes().forEach(({ title }) => {
|
||||
|
||||
@ -31,6 +31,7 @@ const narratorParams = {
|
||||
|
||||
const annoyingParams = {
|
||||
email: "spammy-spammer@example.org",
|
||||
slug: 'spammy-spammer',
|
||||
password: "1234",
|
||||
...termsAndConditionsAgreedVersion
|
||||
};
|
||||
@ -39,8 +40,12 @@ Given("I am logged in", () => {
|
||||
cy.login(loginCredentials);
|
||||
});
|
||||
|
||||
Given("I am logged in as the muted user", () => {
|
||||
cy.login({ email: annoyingParams.email, password: '1234' });
|
||||
Given("the {string} user searches for {string}", (_, postTitle) => {
|
||||
cy.logout()
|
||||
.login({ email: annoyingParams.email, password: '1234' })
|
||||
.get(".searchable-input .ds-select-search")
|
||||
.focus()
|
||||
.type(postTitle);
|
||||
});
|
||||
|
||||
Given("we have a selection of categories", () => {
|
||||
@ -123,6 +128,12 @@ When("I visit the {string} page", page => {
|
||||
cy.openPage(page);
|
||||
});
|
||||
|
||||
When("a blocked user visits the post page of one of my authored posts", () => {
|
||||
cy.logout()
|
||||
.login({ email: annoyingParams.email, password: annoyingParams.password })
|
||||
.openPage('/post/previously-created-post')
|
||||
})
|
||||
|
||||
Given("I am on the {string} page", page => {
|
||||
cy.openPage(page);
|
||||
});
|
||||
@ -212,6 +223,7 @@ Given("we have the following posts in our database:", table => {
|
||||
...postAttributes,
|
||||
deleted: Boolean(postAttributes.deleted),
|
||||
disabled: Boolean(postAttributes.disabled),
|
||||
pinned: Boolean(postAttributes.pinned),
|
||||
categoryIds: ['cat-456']
|
||||
}
|
||||
cy.factory().create("Post", postAttributes);
|
||||
@ -485,7 +497,7 @@ Given("I follow the user {string}", name => {
|
||||
});
|
||||
});
|
||||
|
||||
Given('"Spammy Spammer" wrote a post {string}', title => {
|
||||
Given('{string} wrote a post {string}', (_, title) => {
|
||||
cy.createCategories("cat21")
|
||||
.factory()
|
||||
.create("Post", {
|
||||
@ -500,7 +512,7 @@ Then("the list of posts of this user is empty", () => {
|
||||
cy.get(".main-container").find(".ds-space.hc-empty");
|
||||
});
|
||||
|
||||
Then("nobody is following the user profile anymore", () => {
|
||||
Then("I get removed from his follower collection", () => {
|
||||
cy.get(".ds-card-content").not(".post-link");
|
||||
cy.get(".main-container").contains(
|
||||
".ds-card-content",
|
||||
@ -532,6 +544,20 @@ When("I mute the user {string}", name => {
|
||||
});
|
||||
});
|
||||
|
||||
When("I block the user {string}", name => {
|
||||
cy.neode()
|
||||
.first("User", {
|
||||
name
|
||||
})
|
||||
.then(blockedUser => {
|
||||
cy.neode()
|
||||
.first("User", {
|
||||
name: narratorParams.name
|
||||
})
|
||||
.relateTo(blockedUser, "blocked");
|
||||
});
|
||||
});
|
||||
|
||||
When("I log in with:", table => {
|
||||
const [firstRow] = table.hashes();
|
||||
const {
|
||||
@ -550,3 +576,11 @@ Then("I see only one post with the title {string}", title => {
|
||||
.should("have.length", 1);
|
||||
cy.get(".main-container").contains(".post-link", title);
|
||||
});
|
||||
|
||||
Then("they should not see the comment from", () => {
|
||||
cy.get(".ds-card-footer").children().should('not.have.class', 'comment-form')
|
||||
})
|
||||
|
||||
Then("they should see a text explaining commenting is not possible", () => {
|
||||
cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
|
||||
})
|
||||
@ -9,10 +9,10 @@ Feature: Report and Moderate
|
||||
|
||||
Background:
|
||||
Given we have the following user accounts:
|
||||
| id | name |
|
||||
| u67 | David Irving |
|
||||
| id | name |
|
||||
| u67 | David Irving |
|
||||
| annoying-user | I'm gonna mute Moderators and Admins HA HA HA |
|
||||
|
||||
|
||||
Given we have the following posts in our database:
|
||||
| authorId | id | title | content |
|
||||
| u67 | p1 | The Truth about the Holocaust | It never existed! |
|
||||
|
||||
@ -4,10 +4,10 @@ Feature: Post Comment
|
||||
To be able to express my thoughts and emotions about these, discuss, and add give further information.
|
||||
|
||||
Background:
|
||||
Given 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
|
||||
Given I have a user account
|
||||
And we have the following posts in our database:
|
||||
| id | title | slug | authorId | commentContent |
|
||||
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan | @peter-pan reply to me |
|
||||
And I am logged in
|
||||
|
||||
Scenario: Comment creation
|
||||
@ -36,3 +36,8 @@ Feature: Post Comment
|
||||
Then my comment should be successfully created
|
||||
And I should see an abreviated version of my comment
|
||||
And the editor should be cleared
|
||||
|
||||
Scenario: Direct reply to Comment
|
||||
Given I visit "post/bWBjpkTKZp/101-essays"
|
||||
And I click on the reply button
|
||||
Then it should create a mention in the CommentForm
|
||||
|
||||
46
cypress/integration/user_profile/BlockUser.feature
Normal file
46
cypress/integration/user_profile/BlockUser.feature
Normal file
@ -0,0 +1,46 @@
|
||||
Feature: Block a User
|
||||
As a user
|
||||
I'd like to have a button to block another user
|
||||
To prevent him from seeing and interacting with my contributions
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
And there is an annoying user called "Harassing User"
|
||||
And I am logged in
|
||||
|
||||
Scenario: Block a user
|
||||
Given I am on the profile page of the annoying user
|
||||
When I click on "Block user" from the content menu in the user info box
|
||||
And I navigate to my "Blocked users" settings page
|
||||
Then I can see the following table:
|
||||
| Avatar | Name |
|
||||
| | Harassing User |
|
||||
|
||||
Scenario: Blocked user cannot interact with my contributions
|
||||
Given I block the user "Harassing User"
|
||||
And I previously created a post
|
||||
And a blocked user visits the post page of one of my authored posts
|
||||
Then they should not see the comment from
|
||||
And they should see a text explaining commenting is not possible
|
||||
|
||||
Scenario: Block a previously followed user
|
||||
Given I follow the user "Harassing User"
|
||||
When I visit the profile page of the annoying user
|
||||
And I click on "Block user" from the content menu in the user info box
|
||||
And I get removed from his follower collection
|
||||
|
||||
Scenario: Posts of blocked users are not filtered from search results
|
||||
Given "Harassing User" wrote a post "You can still see my posts"
|
||||
And I block the user "Harassing User"
|
||||
When I search for "see"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| You can still see my posts |
|
||||
|
||||
Scenario: Blocked users can still see my posts
|
||||
Given I previously created a post
|
||||
And I block the user "Harassing User"
|
||||
And the "blocked" user searches for "previously created"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| previously created post |
|
||||
@ -1,8 +1,7 @@
|
||||
Feature: Mute a User
|
||||
As a user
|
||||
I'd like to have a button to mute another user
|
||||
To prevent him from seeing and interacting with my contributions and also to avoid seeing his/her posts
|
||||
|
||||
To prevent him from seeing and interacting with my contributions
|
||||
Background:
|
||||
Given I have a user account
|
||||
And there is an annoying user called "Spammy Spammer"
|
||||
@ -22,9 +21,9 @@ Feature: Mute a User
|
||||
When I visit the profile page of the annoying user
|
||||
And I click on "Mute user" from the content menu in the user info box
|
||||
Then the list of posts of this user is empty
|
||||
And nobody is following the user profile anymore
|
||||
And I get removed from his follower collection
|
||||
|
||||
Scenario: Posts of muted users are filtered from search results
|
||||
Scenario: Posts of muted users are filtered from search results, users are not
|
||||
Given we have the following posts in our database:
|
||||
| id | title | content |
|
||||
| im-not-muted | Post that should be seen | cause I'm not muted |
|
||||
@ -36,18 +35,17 @@ Feature: Mute a User
|
||||
When I mute the user "Spammy Spammer"
|
||||
And I refresh the page
|
||||
And I search for "Spam"
|
||||
Then the search has no results
|
||||
Then the search should not contain posts by the annoying user
|
||||
But the search should contain the annoying user
|
||||
But I search for "not muted"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| Post that should be seen |
|
||||
|
||||
|
||||
Scenario: Muted users can still see my posts
|
||||
Given I previously created a post
|
||||
And I mute the user "Spammy Spammer"
|
||||
Given I log out
|
||||
And I am logged in as the muted user
|
||||
When I search for "previously created"
|
||||
And the "muted" user searches for "previously created"
|
||||
Then I should see the following posts in the select dropdown:
|
||||
| title |
|
||||
| previously created post |
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
/* globals Cypress cy */
|
||||
import "cypress-file-upload";
|
||||
import helpers from "./helpers";
|
||||
import users from "../fixtures/users.json";
|
||||
import { GraphQLClient, request } from 'graphql-request'
|
||||
import { gql } from '../../backend/src/helpers/jest'
|
||||
import config from '../../backend/src/config'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Factory from '../../backend/src/seed/factories'
|
||||
import { getDriver, getNeode } from '../../backend/src/bootstrap/neo4j'
|
||||
import Factory from '../../backend/src/factories'
|
||||
import { getDriver, getNeode } from '../../backend/src/db/neo4j'
|
||||
|
||||
const neo4jConfigs = {
|
||||
uri: Cypress.env('NEO4J_URI'),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// features/support/steps.js
|
||||
import { Given, When, Then, After, AfterAll } from 'cucumber'
|
||||
import Factory from '../../backend/src/seed/factories'
|
||||
import Factory from '../../backend/src/factories'
|
||||
import dotenv from 'dotenv'
|
||||
import expect from 'expect'
|
||||
|
||||
|
||||
0
neo4j/db_manipulation/add_image_aspect_ratio.sh → neo4j/.archived/add_image_aspect_ratio.sh
Executable file → Normal file
0
neo4j/db_manipulation/add_image_aspect_ratio.sh → neo4j/.archived/add_image_aspect_ratio.sh
Executable file → Normal file
@ -23,7 +23,6 @@ DELETE disabled
|
||||
CREATE (moderator)-[review:REVIEWED]->(report:Report)-[:BELONGS_TO]->(disabledResource)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
|
||||
// if disabledResource has no filed report, then create a moderators default filed report
|
||||
WITH moderator, disabledResource, report
|
||||
OPTIONAL MATCH (disabledResourceReporter:User)-[existingFiledReport:FILED]->(disabledResource)
|
||||
@ -36,7 +35,6 @@ FOREACH(disabledResource IN CASE WHEN existingFiledReport IS NOT NULL THEN [1] E
|
||||
SET moveModeratorReport = existingFiledReport
|
||||
DELETE existingFiledReport
|
||||
)
|
||||
|
||||
RETURN disabledResource {.id};
|
||||
" | cypher-shell
|
||||
|
||||
@ -49,7 +47,5 @@ ON CREATE SET report.id = randomUUID(), report.createdAt = toString(datetime()),
|
||||
CREATE (reporter)-[filed:FILED]->(report)
|
||||
SET report = oldReport
|
||||
DELETE oldReport
|
||||
|
||||
RETURN notDisabledResource {.id};
|
||||
" | cypher-shell
|
||||
|
||||
@ -4,7 +4,5 @@ LABEL Description="Neo4J database of the Social Network Human-Connection.org wit
|
||||
ARG BUILD_COMMIT
|
||||
ENV BUILD_COMMIT=$BUILD_COMMIT
|
||||
|
||||
COPY db_setup.sh /usr/local/bin/db_setup
|
||||
|
||||
RUN apt-get update && apt-get -y install wget htop
|
||||
RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.4/apoc-3.5.0.4-all.jar -P plugins/
|
||||
|
||||
@ -18,15 +18,6 @@ docker-compose up
|
||||
You can access Neo4J through [http://localhost:7474/](http://localhost:7474/)
|
||||
for an interactive cypher shell and a visualization of the graph.
|
||||
|
||||
### Database Indices and Constraints
|
||||
|
||||
Database indices and constraints need to be created when the database is
|
||||
running. So start the container with the command above and run:
|
||||
|
||||
```bash
|
||||
docker-compose exec neo4j db_setup
|
||||
```
|
||||
|
||||
|
||||
## Installation without Docker
|
||||
|
||||
@ -45,20 +36,6 @@ Then make sure to allow Apoc procedures by adding the following line to your Neo
|
||||
```
|
||||
dbms.security.procedures.unrestricted=apoc.*
|
||||
```
|
||||
### Database Indices and Constraints
|
||||
|
||||
If you have `cypher-shell` available with your local installation of neo4j you
|
||||
can run:
|
||||
|
||||
```bash
|
||||
# in folder neo4j/
|
||||
$ cp .env.template .env
|
||||
$ ./db_setup.sh
|
||||
```
|
||||
|
||||
Otherwise, if you don't have `cypher-shell` available, copy the cypher
|
||||
statements [from the `db_setup.sh` script](https://github.com/Human-Connection/Human-Connection/blob/master/neo4j/db_setup.sh) and paste the scripts into your
|
||||
[database browser frontend](http://localhost:7474).
|
||||
|
||||
### Alternatives
|
||||
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Database manipulation is not possible without connecting to the database."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "
|
||||
:begin
|
||||
MATCH(user)-[reported:REPORTED]->(resource)
|
||||
WITH reported, resource, COLLECT(user) as users
|
||||
MERGE(report:Report)-[:BELONGS_TO]->(resource)
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
WITH report, users, reported
|
||||
UNWIND users as user
|
||||
MERGE (user)-[filed:FILED]->(report)
|
||||
SET filed = reported
|
||||
DELETE reported;
|
||||
|
||||
MATCH(moderator)-[disabled:DISABLED]->(resource)
|
||||
MATCH(report:Report)-[:BELONGS_TO]->(resource)
|
||||
WITH disabled, resource, COLLECT(moderator) as moderators, report
|
||||
DELETE disabled
|
||||
WITH report, moderators, disabled
|
||||
UNWIND moderators as moderator
|
||||
MERGE (moderator)-[review:REVIEWED {disable: true}]->(report)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true;
|
||||
|
||||
MATCH(moderator)-[disabled:DISABLED]->(resource)
|
||||
WITH disabled, resource, COLLECT(moderator) as moderators
|
||||
MERGE(report:Report)-[:BELONGS_TO]->(resource)
|
||||
SET report.id = randomUUID(), report.createdAt = toString(datetime()), report.updatedAt = report.createdAt, report.rule = 'latestReviewUpdatedAtRules', report.closed = false
|
||||
DELETE disabled
|
||||
WITH report, moderators, disabled
|
||||
UNWIND moderators as moderator
|
||||
MERGE(moderator)-[filed:FILED]->(report)
|
||||
SET filed.createdAt = toString(datetime()), filed.reasonCategory = 'other', filed.reasonDescription = 'Old DISABLED relations didn\'t enforce mandatory reporting !!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.'
|
||||
MERGE (moderator)-[review:REVIEWED {disable: true}]->(report)
|
||||
SET review.createdAt = toString(datetime()), review.updatedAt = review.createdAt, review.disable = true;
|
||||
:commit
|
||||
" | cypher-shell
|
||||
@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_FILE=$(dirname "$0")/.env
|
||||
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
|
||||
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
|
||||
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
|
||||
echo "Setting up database constraints and indexes will probably fail because of authentication errors."
|
||||
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
|
||||
fi
|
||||
|
||||
until echo 'RETURN "Connection successful" as info;' | cypher-shell
|
||||
do
|
||||
echo "Connecting to neo4j failed, trying again..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo '
|
||||
RETURN "Here is a list of indexes and constraints BEFORE THE SETUP:" as info;
|
||||
CALL db.indexes();
|
||||
' | cypher-shell
|
||||
|
||||
echo '
|
||||
CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"]);
|
||||
CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"]);
|
||||
CREATE CONSTRAINT ON (p:Post) ASSERT p.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Comment) ASSERT c.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Category) ASSERT c.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (t:Tag) ASSERT t.id IS UNIQUE;
|
||||
|
||||
CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE;
|
||||
|
||||
CREATE CONSTRAINT ON (e:EmailAddress) ASSERT e.email IS UNIQUE;
|
||||
' | cypher-shell
|
||||
|
||||
echo '
|
||||
RETURN "Setting up all the indexes and constraints seems to have been successful. Here is a list AFTER THE SETUP:" as info;
|
||||
CALL db.indexes();
|
||||
' | cypher-shell
|
||||
12
package.json
12
package.json
@ -25,27 +25,27 @@
|
||||
"release": "standard-version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.3",
|
||||
"@babel/core": "^7.8.4",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/register": "^7.8.3",
|
||||
"auto-changelog": "^1.16.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"codecov": "^3.6.1",
|
||||
"codecov": "^3.6.2",
|
||||
"cross-env": "^6.0.3",
|
||||
"cucumber": "^6.0.5",
|
||||
"cypress": "^3.8.2",
|
||||
"cypress": "^3.8.3",
|
||||
"cypress-cucumber-preprocessor": "^2.0.1",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"cypress-plugin-retries": "^1.5.2",
|
||||
"date-fns": "^2.9.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"expect": "^24.9.0",
|
||||
"expect": "^25.1.0",
|
||||
"faker": "Marak/faker.js#master",
|
||||
"graphql-request": "^1.8.2",
|
||||
"neo4j-driver": "^4.0.1",
|
||||
"neode": "^0.3.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"slug": "^2.1.0",
|
||||
"slug": "^2.1.1",
|
||||
"standard-version": "^7.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
sed -i "s/<COMMIT>/${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml
|
||||
sed -i "s/<COMMIT>/${TRAVIS_COMMIT}/g" $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml
|
||||
kubectl --namespace=human-connection patch configmap configmap -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment web -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
|
||||
kubectl --namespace=human-connection patch configmap develop-configmap -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-configmap.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment develop-backend -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
|
||||
kubectl --namespace=human-connection patch deployment develop-webapp -p "$(cat $TRAVIS_BUILD_DIR/scripts/patches/patch-deployment.yaml)"
|
||||
|
||||
5
webapp/assets/_new/icons/svgs/level-down.svg
Executable file
5
webapp/assets/_new/icons/svgs/level-down.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>level-down</title>
|
||||
<path d="M5 5h17v19.063l4.281-4.281 1.438 1.438-6 6-0.719 0.688-0.719-0.688-6-6 1.438-1.438 4.281 4.281v-17.063h-15v-2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 293 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user