Merge branch 'master' into 2019/kw15/User_can_change_its_username_to_emptystring
# Conflicts: # webapp/locales/de.json # webapp/locales/en.json # webapp/pages/profile/_id/_slug.vue
3
.gitbook.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
structure:
|
||||
readme: README.md
|
||||
summary: SUMMARY.md
|
||||
BIN
.gitbook/assets/grafik (1).png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
.gitbook/assets/grafik-1 (1).png
Normal file
|
After Width: | Height: | Size: 720 KiB |
BIN
.gitbook/assets/grafik-1.png
Normal file
|
After Width: | Height: | Size: 720 KiB |
BIN
.gitbook/assets/grafik-4.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
.gitbook/assets/grafik.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
.gitbook/assets/graphql-playground (1).png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.gitbook/assets/graphql-playground.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.gitbook/assets/humanconnection (1).png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
.gitbook/assets/humanconnection.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
BIN
.gitbook/assets/screenshot (1).png
Normal file
|
After Width: | Height: | Size: 379 KiB |
BIN
.gitbook/assets/screenshot-forking-nitro-backend.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
.gitbook/assets/screenshot-forking-nitro.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 107 KiB |
BIN
.gitbook/assets/screenshot-styleguide (1).png
Normal file
|
After Width: | Height: | Size: 433 KiB |
BIN
.gitbook/assets/screenshot-styleguide (2).png
Normal file
|
After Width: | Height: | Size: 433 KiB |
BIN
.gitbook/assets/screenshot-styleguide.png
Normal file
|
After Width: | Height: | Size: 433 KiB |
BIN
.gitbook/assets/screenshot.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
@ -28,7 +28,7 @@ script:
|
||||
- docker-compose exec webapp yarn run lint
|
||||
- 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
|
||||
- yarn run cypress:run
|
||||
|
||||
after_success:
|
||||
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
|
||||
@ -40,7 +40,5 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.4, available at [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4/)
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
@ -1,40 +1,39 @@
|
||||
Thanks so much for thinking of contributing to the Human Connection project, we really appreciate it! :-)
|
||||
# CONTRIBUTING
|
||||
|
||||
### Getting Set Up
|
||||
Thanks so much for thinking of contributing to the Human Connection project, we really appreciate it! :-\)
|
||||
|
||||
Instructions for how to install all the necessary software can be found in our [documentation](https://docs.human-connection.org/nitro/)
|
||||
## Getting Set Up
|
||||
|
||||
We recommend that new folks should ideally work together with an existing developer. Please join our discord instance to chat with developers or just ask them in tickets in [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f/boards?repos=152252353):
|
||||
Instructions for how to install all the necessary software can be found in our [documentation](https://docs.human-connection.org/human-connection/)
|
||||
|
||||
We recommend that new folks should ideally work together with an existing developer. Please join our discord instance to chat with developers or just ask them in tickets in [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f/boards?repos=152252353):
|
||||
|
||||

|
||||
|
||||
Here are some general notes on our development flow:
|
||||
|
||||
### Development
|
||||
## Development
|
||||
|
||||
* Currently operating in two week sprints
|
||||
* We are using ZenHub to coordinate
|
||||
- estimating time per issue is the crucial feature of [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f) that Github does not have
|
||||
- "up-for-grabs" links to [Github project](https://github.com/orgs/Human-Connection/projects/10?card_filter_query=label%3A%22good+first+issue)
|
||||
- ordering on ZenHub not necessarily reflected on github projects
|
||||
|
||||
* estimating time per issue is the crucial feature of [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f) that Github does not have
|
||||
* "up-for-grabs" links to [Github project](https://github.com/orgs/Human-Connection/projects/10?card_filter_query=label%3A"good+first+issue)
|
||||
* ordering on ZenHub not necessarily reflected on github projects
|
||||
* AgileVentures run open pairing sessions at 10:30am UTC each week on Tuesdays and Thursdays
|
||||
|
||||
* Core team
|
||||
- all the people who are hired by HC non-profit corporation
|
||||
- you can Meet-the-team [every two weeks in German](https://human-connection.org/veranstaltungen/) and [every month in English](https://human-connection.org/en/events/).
|
||||
- 9 people
|
||||
- 2 core developers (Robert [@roschaefer](https://github.com/roschaefer) and Greg [@appinteractive](https://github.com/appinteractive))
|
||||
- 3 marketeers Jasi, Dennis and Sensi
|
||||
- Hardy doing business development
|
||||
- Martin head of IT and previously data protection officer
|
||||
- Victor doing accounting and controlling
|
||||
- Nicolas is the community manager (reviews content in the network) reflects community opinion back to the core team
|
||||
|
||||
* all the people who are hired by HC non-profit corporation
|
||||
* you can Meet-the-team [every two weeks in German](https://human-connection.org/veranstaltungen/) and [every month in English](https://human-connection.org/en/events/).
|
||||
* 9 people
|
||||
* 2 core developers \(Robert [@roschaefer](https://github.com/roschaefer) and Greg [@appinteractive](https://github.com/appinteractive)\)
|
||||
* 3 marketeers Jasi, Dennis and Sensi
|
||||
* Hardy doing business development
|
||||
* Martin head of IT and previously data protection officer
|
||||
* Victor doing accounting and controlling
|
||||
* Nicolas is the community manager \(reviews content in the network\) reflects community opinion back to the core team
|
||||
* when can folks pair with Robert
|
||||
- 10am UTC until 5pm UTC every working day
|
||||
* 10am UTC until 5pm UTC every working day
|
||||
|
||||
### Philosophy
|
||||
## Philosophy
|
||||
|
||||
We practise [collective code ownership](http://www.extremeprogramming.org/rules/collective.html) rather than strong code ownership, which means that:
|
||||
|
||||
@ -45,28 +44,25 @@ We practise [collective code ownership](http://www.extremeprogramming.org/rules/
|
||||
|
||||
Everyone feel free to request merges or answers to issues from the project managers
|
||||
|
||||
But what do we do when waiting for merge into master (wanting to keep PRs small)
|
||||
--> Robert recommends creating a pull request for each step
|
||||
- programming is also about thinking about other people - empathy for your co-workers
|
||||
- but what about when you are waiting for merge?
|
||||
- solutions
|
||||
- 1) put 2nd PR into branch that the first PR is hitting - but requires update after merging
|
||||
- 2) prefer to leave exiting PR until it can be reviewed, and instead go and work on some other part of the codebase that is not impacted by the first PR
|
||||
But what do we do when waiting for merge into master \(wanting to keep PRs small\) --> Robert recommends creating a pull request for each step
|
||||
|
||||
### Notes
|
||||
* programming is also about thinking about other people - empathy for your co-workers
|
||||
* but what about when you are waiting for merge?
|
||||
* solutions
|
||||
* 1\) put 2nd PR into branch that the first PR is hitting - but requires update after merging
|
||||
* 2\) prefer to leave exiting PR until it can be reviewed, and instead go and work on some other part of the codebase that is not impacted by the first PR
|
||||
|
||||
question: when you want to pick a task - (find out priority) - is it in discord? is it in AV slack? --> Robert says you can always ask in discord - group channels are the best
|
||||
## Notes
|
||||
|
||||
Robert shares: [Zenhub board](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/boards?repos=112590397,152252353,152252578,157710732,163305928)
|
||||
Robert says the order of tickets are preserved in ZenHub and reflect their priority (most important at the top) and so check out the current milestones
|
||||
question: when you want to pick a task - \(find out priority\) - is it in discord? is it in AV slack? --> Robert says you can always ask in discord - group channels are the best
|
||||
|
||||
Matt - question about who can work on [ticket 100](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/issues/human-connection/human-connection/100) --> Robert - in rare occasions it might be exclusive to someone with admin permissions
|
||||
Robert: notes greg just pushed this today: https://github.com/Human-Connection/Nitro-Deployment
|
||||
Robert shares: [Zenhub board](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/boards?repos=112590397,152252353,152252578,157710732,163305928) Robert says the order of tickets are preserved in ZenHub and reflect their priority \(most important at the top\) and so check out the current milestones
|
||||
|
||||
Matt - question about who can work on [ticket 100](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/issues/human-connection/human-connection/100) --> Robert - in rare occasions it might be exclusive to someone with admin permissions Robert: notes greg just pushed this today: [https://github.com/Human-Connection/Nitro-Deployment](https://github.com/Human-Connection/Nitro-Deployment)
|
||||
|
||||
Matt makes point that new stories will have to be taken off the "New Issues" and Robert says that's fine, if you don't like the first one, then you can take the next one. Volunteeers have no commitment except their own self development and their awesomeness by contributing to free and open-source software projects.
|
||||
|
||||
Robert notes that everyone is invited to join the kickoff meetings
|
||||
|
||||
Robert - difference between "important" (creates a lot of value) and "beginner friendly" (easy to implement)
|
||||
|
||||
Robert - difference between "important" \(creates a lot of value\) and "beginner friendly" \(easy to implement\)
|
||||
|
||||
|
||||
23
LICENSE.md
@ -1,21 +1,12 @@
|
||||
# LICENSE
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Human-Connection gGmbH
|
||||
Copyright \(c\) 2018 Human-Connection gGmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files \(the "Software"\), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
58
README.md
@ -1,20 +1,25 @@
|
||||
<p align="center">
|
||||
<a href="https://human-connection.org"><img align="center" src="https://github.com/Human-Connection/Human-Connection/blob/master/lets_get_together_2.png" alt="Human-Connection" /></a>
|
||||
</p>
|
||||
|
||||
# Human-Connection
|
||||
|
||||
|
||||
[](https://travis-ci.com/Human-Connection/Human-Connection)
|
||||
[](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md)
|
||||
[](https://discord.gg/6ub73U3)
|
||||
|
||||
Human Connection is a free and open-source social network for active citizenship.
|
||||
Human Connection is a nonprofit social, action and knowledge network that connects information to action and promotes positive local and global change in all areas of life.
|
||||
|
||||
* **Social**: Interact with other people not just by commenting their posts, but by providing **Pro & Contra** arguments, give a **Versus** or ask them by integrated **Chat** or **Let's Talk**
|
||||
* **Knowledge**: Read articles about interesting topics and find related posts in the **More Info** tab or by **Filtering** based on **Categories** and **Tagging** or by using the **Fulltext Search**.
|
||||
* **Action**: Don't just read about how to make the world a better place, but come into **Action** by following provided suggestions on the **Action** tab provided by other people or **Organisations**.
|
||||
|
||||
[](https://human-connection.org)
|
||||
|
||||
**Technology Stack**
|
||||
- [VueJS](https://vuejs.org/)
|
||||
- [NuxtJS](https://nuxtjs.org/)
|
||||
- [GraphQL](https://graphql.org/)
|
||||
- [NodeJS](https://nodejs.org/en/)
|
||||
- [Neo4J](https://neo4j.com/)
|
||||
|
||||
* [VueJS](https://vuejs.org/)
|
||||
* [NuxtJS](https://nuxtjs.org/)
|
||||
* [GraphQL](https://graphql.org/)
|
||||
* [NodeJS](https://nodejs.org/en/)
|
||||
* [Neo4J](https://neo4j.com/)
|
||||
|
||||
|
||||
## Live demo
|
||||
|
||||
@ -22,18 +27,31 @@ Try out our deployed [staging environment](https://nitro-staging.human-connectio
|
||||
|
||||
Logins:
|
||||
|
||||
| email | password | role |
|
||||
| --- | --- | --- |
|
||||
| `user@example.org` | 1234 | user |
|
||||
| `moderator@example.org` | 1234 | moderator |
|
||||
| `admin@example.org` | 1234 | admin |
|
||||
|
||||
| email | password | role |
|
||||
| :--- | :--- | :--- |
|
||||
| `user@example.org` | 1234 | user |
|
||||
| `moderator@example.org` | 1234 | moderator |
|
||||
| `admin@example.org` | 1234 | admin |
|
||||
|
||||
## Documentation
|
||||
Learn how to set up a local development environment in our [Docs](https://docs.human-connection.org/nitro).
|
||||
|
||||
Learn how to set up a local development environment in our [Docs](https://docs.human-connection.org/human-connection/) :mag_right:
|
||||
|
||||
## Translations
|
||||
Contributre translations on [lokalise.co](https://lokalise.co/public/556252725c18dd752dd546.13222042/).
|
||||
|
||||
You can help translating the interface by joining us on [lokalise.co](https://lokalise.co/public/556252725c18dd752dd546.13222042/).
|
||||
Thank you lokalise for providing us with a premium account :raised_hands:.
|
||||
|
||||
## Developer Chat
|
||||
Join the open-source community on [Discord](https://discord.gg/6ub73U3).
|
||||
|
||||
Join our friendly open-source community on [Discord](https://discord.gg/6ub73U3) :heart_eyes_cat:
|
||||
Just introduce yourself at `#user-presentation` and mention `@@Mentor` to get you onboard :neckbeard:
|
||||
Check out the [contribution guideline](./CONTRIBUTING.md), too!
|
||||
|
||||
|
||||
## Attributions
|
||||
|
||||
Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)
|
||||
|
||||
## License
|
||||
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
|
||||
|
||||
32
SUMMARY.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Table of contents
|
||||
|
||||
* [Introduction](README.md)
|
||||
* [Edit this Documentation](edit-this-documentation.md)
|
||||
* [Installation](installation.md)
|
||||
* [Backend](backend/README.md)
|
||||
* [graphql-with-apollo](backend/graphql-with-apollo/README.md)
|
||||
* [GraphQL with Apollo](backend/graphql-with-apollo/graphql-with-apollo/README.md)
|
||||
* [Mocking](backend/graphql-with-apollo/graphql-with-apollo/mocking.md)
|
||||
* [Seeding](backend/graphql-with-apollo/graphql-with-apollo/seeding.md)
|
||||
* [Import](backend/data-import.md)
|
||||
* [Middleware](backend/middleware.md)
|
||||
* [Webapp](webapp/README.md)
|
||||
* [COMPONENTS](webapp/components.md)
|
||||
* [PLUGINS](webapp/plugins.md)
|
||||
* [STORE](webapp/store.md)
|
||||
* [PAGES](webapp/pages.md)
|
||||
* [ASSETS](webapp/assets.md)
|
||||
* [LAYOUTS](webapp/layouts.md)
|
||||
* [Styleguide](webapp/styleguide.md)
|
||||
* [STATIC](webapp/static.md)
|
||||
* [MIDDLEWARE](webapp/middleware.md)
|
||||
* [Testing Guide](testing.md)
|
||||
* [End-to-end tests](cypress/README.md)
|
||||
* [Frontend tests](webapp/testing.md)
|
||||
* [Backend tests](backend/testing.md)
|
||||
* [Contributing](CONTRIBUTING.md)
|
||||
* [Deployment](deployment/README.md)
|
||||
* [Feature Specification](cypress/features.md)
|
||||
* [Code of conduct](CODE_OF_CONDUCT.md)
|
||||
* [License](LICENSE.md)
|
||||
|
||||
@ -1,53 +1,17 @@
|
||||
<p align="center">
|
||||
<a href="https://human-connection.org"><img align="center" src="humanconnection.png" height="200" alt="Human Connection" /></a>
|
||||
</p>
|
||||
# Backend
|
||||
|
||||
# NITRO Backend
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Backend)
|
||||
[](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_shield)
|
||||
[](https://discord.gg/6ub73U3)
|
||||
|
||||
> This Prototype tries to resolve the biggest hurdle of connecting
|
||||
> our services together. This is not possible in a sane way using
|
||||
> our current approach.
|
||||
>
|
||||
> With this Prototype we can explore using the combination of
|
||||
> GraphQL and the Neo4j Graph Database for achieving the connected
|
||||
> nature of a social graph with better development experience as we
|
||||
> do not need to connect data by our own any more through weird table
|
||||
> structures etc.
|
||||
## Installation with Docker
|
||||
|
||||
>
|
||||
> #### Advantages:
|
||||
> - easer data structure
|
||||
> - better connected data
|
||||
> - easy to achieve "recommendations" based on actions (relations)
|
||||
> - more performant and better to understand API
|
||||
> - better API client that uses caching
|
||||
>
|
||||
> We still need to evaluate the drawbacks and estimate the development
|
||||
> cost of such an approach
|
||||
Make sure you are on a [node](https://nodejs.org/en/) version >= `v10.12.0`:
|
||||
|
||||
## How to get in touch
|
||||
Connect with other developers over [Discord](https://discord.gg/6ub73U3)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
Node >= `v10.12.0`
|
||||
```
|
||||
```text
|
||||
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`
|
||||
|
||||
### Installation with Docker
|
||||
|
||||
Run:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
|
||||
# create indices etc.
|
||||
@ -61,12 +25,12 @@ docker-compose exec backend yarn run db:seed
|
||||
App is [running on port 4000](http://localhost:4000/)
|
||||
|
||||
To wipe out your neo4j database run:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
|
||||
### Installation without Docker
|
||||
## Installation without Docker
|
||||
|
||||
Install dependencies:
|
||||
|
||||
@ -75,11 +39,12 @@ Download [Neo4j Community Edition](https://neo4j.com/download-center/#releases)
|
||||
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
|
||||
```
|
||||
|
||||
```text
|
||||
neo4j\bin\neo4j start
|
||||
```
|
||||
and confirm it's running [here](http://localhost:7474)
|
||||
|
||||
and confirm it's running [here](http://localhost:7474)
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
@ -88,9 +53,11 @@ npm install
|
||||
```
|
||||
|
||||
Copy:
|
||||
```
|
||||
|
||||
```text
|
||||
cp .env.template .env
|
||||
```
|
||||
|
||||
Configure the file `.env` according to your needs and your local setup.
|
||||
|
||||
Start the GraphQL service:
|
||||
@ -109,15 +76,13 @@ yarn start
|
||||
npm start
|
||||
```
|
||||
|
||||
This will start the GraphQL service (by default on localhost:4000)
|
||||
where you can issue GraphQL requests or access GraphQL Playground in the browser:
|
||||
This will start the GraphQL service \(by default on localhost:4000\) where you can issue GraphQL requests or access GraphQL Playground in the browser:
|
||||
|
||||

|
||||

|
||||
|
||||
## Configure
|
||||
|
||||
Set your Neo4j connection string and credentials in `.env`.
|
||||
For example:
|
||||
Set your Neo4j connection string and credentials in `.env`. For example:
|
||||
|
||||
_.env_
|
||||
|
||||
@ -129,24 +94,11 @@ NEO4J_PASSWORD=letmein
|
||||
|
||||
> You need to install APOC as a plugin for the graph you create in the neo4j desktop app!
|
||||
|
||||
Note that grand-stack-starter does not currently bundle a distribution
|
||||
of Neo4j. You can download [Neo4j Desktop](https://neo4j.com/download/)
|
||||
and run locally for development, spin up a [hosted Neo4j Sandbox instance](https://neo4j.com/download/),
|
||||
run Neo4j in one of the [many cloud options](https://neo4j.com/developer/guide-cloud-deployment/),
|
||||
[spin up Neo4j in a Docker container](https://neo4j.com/developer/docker/) or on Debian-based systems install [Neo4j from the Debian Repository](http://debian.neo4j.org/).
|
||||
Just be sure to update the Neo4j connection string and credentials accordingly in `.env`.
|
||||
Note that grand-stack-starter does not currently bundle a distribution of Neo4j. You can download [Neo4j Desktop](https://neo4j.com/download/) and run locally for development, spin up a [hosted Neo4j Sandbox instance](https://neo4j.com/download/), run Neo4j in one of the [many cloud options](https://neo4j.com/developer/guide-cloud-deployment/), [spin up Neo4j in a Docker container](https://neo4j.com/developer/docker/) or on Debian-based systems install [Neo4j from the Debian Repository](http://debian.neo4j.org/). Just be sure to update the Neo4j connection string and credentials accordingly in `.env`.
|
||||
|
||||
## Mock API Results
|
||||
# Seed and Reset the Database
|
||||
|
||||
Alternatively you can just mock all responses from the api which let
|
||||
you build a frontend application without running a neo4j instance.
|
||||
|
||||
Just set `MOCK=true` inside `.env` or pass it on application start.
|
||||
|
||||
## Seed and Reset the Database
|
||||
|
||||
Optionally you can seed the GraphQL service by executing mutations that
|
||||
will write sample data to the database:
|
||||
Optionally you can seed the GraphQL service by executing mutations that will write sample data to the database:
|
||||
|
||||
```bash
|
||||
yarn run db:seed
|
||||
@ -162,17 +114,20 @@ yarn db:reset
|
||||
npm run db:reset
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
# Testing
|
||||
|
||||
**Beware**: We have no multiple database setup at the moment. We clean the database after each test, running the tests will wipe out all your data!
|
||||
|
||||
Run the **_jest_** tests:
|
||||
Run the _**jest**_ tests:
|
||||
|
||||
```bash
|
||||
yarn run test
|
||||
# -or-
|
||||
npm run test
|
||||
```
|
||||
Run the **_cucumber_** features:
|
||||
|
||||
Run the _**cucumber**_ features:
|
||||
|
||||
```bash
|
||||
yarn run test:cucumber
|
||||
# -or-
|
||||
@ -180,17 +135,3 @@ npm run test:cucumber
|
||||
```
|
||||
|
||||
When some tests fail, try `yarn db:reset` and after that `yarn db:seed`. Then run the tests again
|
||||
## Todo`s
|
||||
|
||||
- [x] add jwt authentication
|
||||
- [ ] get directives working correctly (@toLower, @auth, @role, etc.)
|
||||
- [x] check if search is working
|
||||
- [x] check if sorting is working
|
||||
- [x] check if pagination is working
|
||||
- [ ] check if upload is working (using graphql-yoga?)
|
||||
- [x] evaluate middleware
|
||||
- [ ] ignore Posts and Comments by blacklisted Users
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_large)
|
||||
|
||||
38
backend/data-import.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Import
|
||||
|
||||
This guide helps you to import data from our legacy servers, which are using FeathersJS and MongoDB.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need [docker](https://www.docker.com/) installed on your machine. Furthermore you need SSH access to the server and you need to know the following login credentials and server settings:
|
||||
|
||||
| Environment variable | Description |
|
||||
| :--- | :--- |
|
||||
| SSH\_USERNAME | Your ssh username on the server |
|
||||
| SSH\_HOST | The IP address of the server |
|
||||
| MONGODB\_USERNAME | Mongo username on the server |
|
||||
| MONGODB\_PASSWORD | Mongo password on the server |
|
||||
| MONGODB\_AUTH\_DB | Mongo authentication database |
|
||||
| MONGODB\_DATABASE | The name of the mongo database |
|
||||
| UPLOADS\_DIRECTORY | Path to remote uploads folder |
|
||||
|
||||
## Run the database migration
|
||||
|
||||
Run `docker-compose` with all environment variables specified:
|
||||
|
||||
```bash
|
||||
SSH_USERNAME=username SSH_HOST=some.server.com MONGODB_USERNAME='hc-api' MONGODB_PASSWORD='secret' MONGODB_DATABASE=hc_api MONGODB_AUTH_DB=hc_api UPLOADS_DIRECTORY=/var/www/api/uploads docker-compose up
|
||||
```
|
||||
|
||||
Download the remote mongo database:
|
||||
|
||||
```bash
|
||||
docker-compose exec db-migration-worker ./import.sh
|
||||
```
|
||||
|
||||
Import the local download into Neo4J:
|
||||
|
||||
```bash
|
||||
docker-compose exec neo4j import/import.sh
|
||||
```
|
||||
|
||||
2
backend/graphql-with-apollo/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# graphql-with-apollo
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
# GraphQL with Apollo
|
||||
|
||||
GraphQL is a data query language which provides an alternative to REST and ad-hoc web service architectures. It allows clients to define the structure of the data required, and exactly the same structure of the data is returned from the server.
|
||||
|
||||

|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
# Mocking
|
||||
|
||||
## Mocking API Results
|
||||
|
||||
Alternatively you can just mock all responses from the api which let you build a frontend application without running a neo4j instance.
|
||||
|
||||
Just set `MOCK=true` inside `.env` or pass it on application start.
|
||||
|
||||
20
backend/graphql-with-apollo/graphql-with-apollo/seeding.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Seeding
|
||||
|
||||
## Seeding The Database
|
||||
|
||||
Optionally you can seed the GraphQL service by executing mutations that will write sample data to the database:
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Yarn" %}
|
||||
```bash
|
||||
yarn db:seed
|
||||
```
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="NPM" %}
|
||||
```bash
|
||||
npm run db:seed
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
12
backend/middleware.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Middleware
|
||||
|
||||

|
||||
|
||||
## Middleware keeps resolvers clean
|
||||
|
||||
A well-organized codebase is key for the ability to maintain and easily introduce changes into an app. Figuring out the right structure for your code remains a continuous challenge - especially as an application grows and more developers are joining a project.
|
||||
|
||||
A common problem in GraphQL servers is that resolvers often get cluttered with business logic, making the entire resolver system harder to understand and maintain.
|
||||
|
||||
GraphQL Middleware uses the [_middleware pattern_](https://dzone.com/articles/understanding-middleware-pattern-in-expressjs) \(well-known from Express.js\) to pull out repetitive code from resolvers and execute it before or after one of your resolvers is invoked. This improves code modularity and keeps your resolvers clean and simple.
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
FROM neo4j:3.5.0
|
||||
FROM neo4j:3.5.4
|
||||
RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.1/apoc-3.5.0.1-all.jar -P plugins/
|
||||
COPY migrate.sh /usr/local/bin/migrate
|
||||
|
||||
@ -11,13 +11,12 @@
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"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:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev 2> /dev/null",
|
||||
"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": " 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:cucumber": " cross-env CLIENT_URI=http://localhost:4123 run-p --race test:before:* 'test:cucumber:cmd {@}' --",
|
||||
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
|
||||
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
|
||||
"db:reset": "babel-node src/seed/reset-db.js",
|
||||
@ -51,7 +50,7 @@
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.3.1",
|
||||
"graphql-shield": "~5.3.3",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"helmet": "~3.16.0",
|
||||
@ -71,7 +70,7 @@
|
||||
"wait-on": "~3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.2.3",
|
||||
"@babel/cli": "~7.4.3",
|
||||
"@babel/core": "~7.4.3",
|
||||
"@babel/node": "~7.2.2",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
@ -85,14 +84,14 @@
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.16.0",
|
||||
"eslint-plugin-import": "~2.17.1",
|
||||
"eslint-plugin-jest": "~22.4.1",
|
||||
"eslint-plugin-node": "~8.0.1",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
"eslint-plugin-standard": "~4.0.0",
|
||||
"graphql-request": "~1.8.2",
|
||||
"jest": "~24.7.1",
|
||||
"nodemon": "~1.18.10",
|
||||
"nodemon": "~1.18.11",
|
||||
"supertest": "~4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,11 @@ 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 follow from './resolvers/follow.js'
|
||||
import shout from './resolvers/shout.js'
|
||||
import rewards from './resolvers/rewards.js'
|
||||
import socialMedia from './resolvers/socialMedia.js'
|
||||
import notifications from './resolvers/notifications'
|
||||
|
||||
export const typeDefs = fs
|
||||
.readFileSync(
|
||||
@ -17,13 +21,18 @@ export const typeDefs = fs
|
||||
export const resolvers = {
|
||||
Query: {
|
||||
...statistics.Query,
|
||||
...userManagement.Query
|
||||
...userManagement.Query,
|
||||
...notifications.Query
|
||||
},
|
||||
Mutation: {
|
||||
...userManagement.Mutation,
|
||||
...reports.Mutation,
|
||||
...posts.Mutation,
|
||||
...moderation.Mutation,
|
||||
...rewards.Mutation
|
||||
...follow.Mutation,
|
||||
...shout.Mutation,
|
||||
...rewards.Mutation,
|
||||
...socialMedia.Mutation,
|
||||
...notifications.Mutation
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,15 +10,19 @@ import permissionsMiddleware from './permissionsMiddleware'
|
||||
import userMiddleware from './userMiddleware'
|
||||
import includedFieldsMiddleware from './includedFieldsMiddleware'
|
||||
import orderByMiddleware from './orderByMiddleware'
|
||||
import validUrlMiddleware from './validUrlMiddleware'
|
||||
import notificationsMiddleware from './notificationsMiddleware'
|
||||
|
||||
export default schema => {
|
||||
let middleware = [
|
||||
passwordMiddleware,
|
||||
dateTimeMiddleware,
|
||||
validUrlMiddleware,
|
||||
sluggifyMiddleware,
|
||||
excerptMiddleware,
|
||||
xssMiddleware,
|
||||
fixImageUrlsMiddleware,
|
||||
notificationsMiddleware,
|
||||
softDeleteMiddleware,
|
||||
userMiddleware,
|
||||
includedFieldsMiddleware,
|
||||
|
||||
10
backend/src/middleware/notifications/mentions.js
Normal file
@ -0,0 +1,10 @@
|
||||
const MENTION_REGEX = /\s@([\w_-]+)/g
|
||||
|
||||
export function extractSlugs (content) {
|
||||
let slugs = []
|
||||
let match
|
||||
while ((match = MENTION_REGEX.exec(content)) != null) {
|
||||
slugs.push(match[1])
|
||||
}
|
||||
return slugs
|
||||
}
|
||||
30
backend/src/middleware/notifications/mentions.spec.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { extractSlugs } from './mentions'
|
||||
|
||||
describe('extract', () => {
|
||||
describe('finds mentions in the form of', () => {
|
||||
it('@user', () => {
|
||||
const content = 'Hello @user'
|
||||
expect(extractSlugs(content)).toEqual(['user'])
|
||||
})
|
||||
|
||||
it('@user-with-dash', () => {
|
||||
const content = 'Hello @user-with-dash'
|
||||
expect(extractSlugs(content)).toEqual(['user-with-dash'])
|
||||
})
|
||||
|
||||
it('@user.', () => {
|
||||
const content = 'Hello @user.'
|
||||
expect(extractSlugs(content)).toEqual(['user'])
|
||||
})
|
||||
|
||||
it('@user-With-Capital-LETTERS', () => {
|
||||
const content = 'Hello @user-With-Capital-LETTERS'
|
||||
expect(extractSlugs(content)).toEqual(['user-With-Capital-LETTERS'])
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores email addresses', () => {
|
||||
const content = 'Hello somebody@example.org'
|
||||
expect(extractSlugs(content)).toEqual([])
|
||||
})
|
||||
})
|
||||
27
backend/src/middleware/notificationsMiddleware.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { extractSlugs } from './notifications/mentions'
|
||||
|
||||
const notify = async (resolve, root, args, context, resolveInfo) => {
|
||||
const post = await resolve(root, args, context, resolveInfo)
|
||||
|
||||
const session = context.driver.session()
|
||||
const { content, id: postId } = post
|
||||
const slugs = extractSlugs(content)
|
||||
const createdAt = (new Date()).toISOString()
|
||||
const cypher = `
|
||||
match(u:User) where u.slug in $slugs
|
||||
match(p:Post) where p.id = $postId
|
||||
create(n:Notification{id: apoc.create.uuid(), read: false, createdAt: $createdAt})
|
||||
merge (n)-[:NOTIFIED]->(u)
|
||||
merge (p)-[:NOTIFIED]->(n)
|
||||
`
|
||||
await session.run(cypher, { slugs, createdAt, postId })
|
||||
session.close()
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreatePost: notify
|
||||
}
|
||||
}
|
||||
85
backend/src/middleware/notificationsMiddleware.spec.js
Normal file
@ -0,0 +1,85 @@
|
||||
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',
|
||||
name: 'Al Capone',
|
||||
slug: 'al-capone',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('currentUser { notifications }', () => {
|
||||
const query = `query($read: Boolean) {
|
||||
currentUser {
|
||||
notifications(read: $read, orderBy: createdAt_desc) {
|
||||
read
|
||||
post {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe('given another user', () => {
|
||||
let authorClient
|
||||
let authorParams
|
||||
let authorHeaders
|
||||
|
||||
beforeEach(async () => {
|
||||
authorParams = {
|
||||
email: 'author@example.org',
|
||||
password: '1234',
|
||||
id: 'author'
|
||||
}
|
||||
await factory.create('User', authorParams)
|
||||
authorHeaders = await login(authorParams)
|
||||
})
|
||||
|
||||
describe('who mentions me in a post', () => {
|
||||
beforeEach(async () => {
|
||||
const content = 'Hey @al-capone how do you do?'
|
||||
const title = 'Mentioning Al Capone'
|
||||
const createPostMutation = `
|
||||
mutation($title: String!, $content: String!) {
|
||||
CreatePost(title: $title, content: $content) {
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
authorClient = new GraphQLClient(host, { headers: authorHeaders })
|
||||
await authorClient.request(createPostMutation, { title, content })
|
||||
})
|
||||
|
||||
it('sends you a notification', async () => {
|
||||
const expected = {
|
||||
currentUser: {
|
||||
notifications: [
|
||||
{ read: false, post: { content: 'Hey @al-capone how do you do?' } }
|
||||
]
|
||||
}
|
||||
}
|
||||
await expect(client.request(query, { read: false })).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -11,10 +11,11 @@ export default {
|
||||
}
|
||||
},
|
||||
Query: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
return walkRecursive(result, ['password'], () => {
|
||||
let result = await resolve(root, args, context, info)
|
||||
result = walkRecursive(result, ['password', 'privateKey'], () => {
|
||||
// replace password with asterisk
|
||||
return '*****'
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,21 @@ const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info)
|
||||
return context.user.id === parent.id
|
||||
})
|
||||
|
||||
const belongsToMe = rule({ cache: 'no_cache' })(async (_, args, context) => {
|
||||
const { driver, user: { id: userId } } = context
|
||||
const { id: notificationId } = args
|
||||
const session = driver.session()
|
||||
const result = await session.run(`
|
||||
MATCH (u:User {id: $userId})<-[:NOTIFIED]-(n:Notification {id: $notificationId})
|
||||
RETURN n
|
||||
`, { userId, notificationId })
|
||||
const [notification] = result.records.map((record) => {
|
||||
return record.get('n')
|
||||
})
|
||||
session.close()
|
||||
return Boolean(notification)
|
||||
})
|
||||
|
||||
const onlyEnabledContent = rule({ cache: 'strict' })(async (parent, args, ctx, info) => {
|
||||
const { disabled, deleted } = args
|
||||
return !(disabled || deleted)
|
||||
@ -50,6 +65,7 @@ const permissions = shield({
|
||||
Post: or(onlyEnabledContent, isModerator)
|
||||
},
|
||||
Mutation: {
|
||||
UpdateNotification: belongsToMe,
|
||||
CreatePost: isAuthenticated,
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
@ -58,6 +74,7 @@ const permissions = shield({
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
AddUserBadges: isAdmin,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
@ -74,7 +91,8 @@ const permissions = shield({
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
password: isMyOwn
|
||||
password: isMyOwn,
|
||||
privateKey: isMyOwn
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
18
backend/src/middleware/validUrlMiddleware.js
Normal file
@ -0,0 +1,18 @@
|
||||
const validURL = str => {
|
||||
const isValid = str.match(/^(?:https?:\/\/)(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g)
|
||||
return !!isValid
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (resolve, root, args, context, info) => {
|
||||
let socialMedia
|
||||
if (validURL(args.url)) {
|
||||
socialMedia = await resolve(root, args, context, info)
|
||||
} else {
|
||||
throw Error('Input is not a URL')
|
||||
}
|
||||
return socialMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
51
backend/src/resolvers/follow.js
Normal file
@ -0,0 +1,51 @@
|
||||
export default {
|
||||
Mutation: {
|
||||
follow: async (_object, params, context, _resolveInfo) => {
|
||||
const { id, type } = params
|
||||
|
||||
const session = context.driver.session()
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (node {id: $id}), (user:User {id: $userId})
|
||||
WHERE $type IN labels(node) AND NOT $id = $userId
|
||||
MERGE (user)-[relation:FOLLOWS]->(node)
|
||||
RETURN COUNT(relation) > 0 as isFollowed`,
|
||||
{
|
||||
id,
|
||||
type,
|
||||
userId: context.user.id
|
||||
}
|
||||
)
|
||||
|
||||
const [isFollowed] = transactionRes.records.map(record => {
|
||||
return record.get('isFollowed')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return isFollowed
|
||||
},
|
||||
|
||||
unfollow: async (_object, params, context, _resolveInfo) => {
|
||||
const { id, type } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (user:User {id: $userId})-[relation:FOLLOWS]->(node {id: $id})
|
||||
WHERE $type IN labels(node)
|
||||
DELETE relation
|
||||
RETURN COUNT(relation) > 0 as isFollowed`,
|
||||
{
|
||||
id,
|
||||
type,
|
||||
userId: context.user.id
|
||||
}
|
||||
)
|
||||
const [isFollowed] = transactionRes.records.map(record => {
|
||||
return record.get('isFollowed')
|
||||
})
|
||||
session.close()
|
||||
|
||||
return isFollowed
|
||||
}
|
||||
}
|
||||
}
|
||||
14
backend/src/resolvers/notifications.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Notification: (object, params, context, resolveInfo) => {
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
UpdateNotification: (object, params, context, resolveInfo) => {
|
||||
return neo4jgraphql(object, params, context, resolveInfo, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,14 @@ import { host, login } from '../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
let userParams = {
|
||||
id: 'you',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
id: 'you',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
await factory.create('User', userParams)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@ -118,3 +119,63 @@ describe('currentUser { notifications }', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('UpdateNotification', () => {
|
||||
const mutation = `mutation($id: ID!, $read: Boolean){
|
||||
UpdateNotification(id: $id, read: $read) {
|
||||
id read
|
||||
}
|
||||
}`
|
||||
const variables = { id: 'to-be-updated', read: true }
|
||||
|
||||
describe('given a notifications', () => {
|
||||
let headers
|
||||
|
||||
beforeEach(async () => {
|
||||
const mentionedParams = {
|
||||
id: 'mentioned-1',
|
||||
email: 'mentioned@example.org',
|
||||
password: '1234',
|
||||
slug: 'mentioned'
|
||||
}
|
||||
await factory.create('User', mentionedParams)
|
||||
await factory.create('Notification', { id: 'to-be-updated' })
|
||||
await factory.authenticateAs(userParams)
|
||||
await factory.create('Post', { id: 'p1' })
|
||||
await Promise.all([
|
||||
factory.relate('Notification', 'User', { from: 'to-be-updated', to: 'mentioned-1' }),
|
||||
factory.relate('Notification', 'Post', { from: 'p1', to: 'to-be-updated' })
|
||||
])
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('throws authorization error', async () => {
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
|
||||
describe('and owner', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'mentioned@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('updates notification', async () => {
|
||||
const expected = { UpdateNotification: { id: 'to-be-updated', read: true } }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,7 +4,7 @@ export default {
|
||||
const { fromBadgeId, toUserId } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let sessionRes = await session.run(
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (badge:Badge {id: $badgeId}), (rewardedUser:User {id: $rewardedUserId})
|
||||
MERGE (badge)-[:REWARDED]->(rewardedUser)
|
||||
RETURN rewardedUser {.id}`,
|
||||
@ -14,7 +14,7 @@ export default {
|
||||
}
|
||||
)
|
||||
|
||||
const [rewardedUser] = sessionRes.records.map(record => {
|
||||
const [rewardedUser] = transactionRes.records.map(record => {
|
||||
return record.get('rewardedUser')
|
||||
})
|
||||
|
||||
@ -27,7 +27,7 @@ export default {
|
||||
const { fromBadgeId, toUserId } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let sessionRes = await session.run(
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (badge:Badge {id: $badgeId})-[reward:REWARDED]->(rewardedUser:User {id: $rewardedUserId})
|
||||
DELETE reward
|
||||
RETURN rewardedUser {.id}`,
|
||||
@ -36,7 +36,7 @@ export default {
|
||||
rewardedUserId: toUserId
|
||||
}
|
||||
)
|
||||
const [rewardedUser] = sessionRes.records.map(record => {
|
||||
const [rewardedUser] = transactionRes.records.map(record => {
|
||||
return record.get('rewardedUser')
|
||||
})
|
||||
session.close()
|
||||
|
||||
51
backend/src/resolvers/shout.js
Normal file
@ -0,0 +1,51 @@
|
||||
export default {
|
||||
Mutation: {
|
||||
shout: async (_object, params, context, _resolveInfo) => {
|
||||
const { id, type } = params
|
||||
|
||||
const session = context.driver.session()
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (node {id: $id})<-[:WROTE]-(userWritten:User), (user:User {id: $userId})
|
||||
WHERE $type IN labels(node) AND NOT userWritten.id = $userId
|
||||
MERGE (user)-[relation:SHOUTED]->(node)
|
||||
RETURN COUNT(relation) > 0 as isShouted`,
|
||||
{
|
||||
id,
|
||||
type,
|
||||
userId: context.user.id
|
||||
}
|
||||
)
|
||||
|
||||
const [isShouted] = transactionRes.records.map(record => {
|
||||
return record.get('isShouted')
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return isShouted
|
||||
},
|
||||
|
||||
unshout: async (_object, params, context, _resolveInfo) => {
|
||||
const { id, type } = params
|
||||
const session = context.driver.session()
|
||||
|
||||
let transactionRes = await session.run(
|
||||
`MATCH (user:User {id: $userId})-[relation:SHOUTED]->(node {id: $id})
|
||||
WHERE $type IN labels(node)
|
||||
DELETE relation
|
||||
RETURN COUNT(relation) > 0 as isShouted`,
|
||||
{
|
||||
id,
|
||||
type,
|
||||
userId: context.user.id
|
||||
}
|
||||
)
|
||||
const [isShouted] = transactionRes.records.map(record => {
|
||||
return record.get('isShouted')
|
||||
})
|
||||
session.close()
|
||||
|
||||
return isShouted
|
||||
}
|
||||
}
|
||||
}
|
||||
21
backend/src/resolvers/socialMedia.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, true)
|
||||
const session = context.driver.session()
|
||||
await session.run(
|
||||
`MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId})
|
||||
MERGE (socialMedia)<-[:OWNED]-(owner)
|
||||
RETURN owner`, {
|
||||
userId: context.user.id,
|
||||
socialMediaId: socialMedia.id
|
||||
}
|
||||
)
|
||||
session.close()
|
||||
|
||||
return socialMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
49
backend/src/resolvers/socialMedia.spec.js
Normal file
@ -0,0 +1,49 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login } from '../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
describe('CreateSocialMedia', () => {
|
||||
let client
|
||||
let headers
|
||||
const mutation = `
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
|
||||
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
|
||||
name: 'Matilde Hermiston',
|
||||
slug: 'matilde-hermiston',
|
||||
role: 'user',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('rejects empty string', async () => {
|
||||
const variables = { url: '' }
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL')
|
||||
})
|
||||
|
||||
it('validates URLs', async () => {
|
||||
const variables = { url: 'not-a-url' }
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,3 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import Factory from '../seed/factories'
|
||||
import { GraphQLClient, request } from 'graphql-request'
|
||||
import jwt from 'jsonwebtoken'
|
||||
@ -254,7 +255,7 @@ describe('change password', () => {
|
||||
}
|
||||
|
||||
describe('should be authenticated before changing password', () => {
|
||||
it('throws not "Not Authorised!', async () => {
|
||||
it('throws "Not Authorised!"', async () => {
|
||||
await expect(
|
||||
request(
|
||||
host,
|
||||
@ -309,3 +310,97 @@ describe('change password', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('do not expose private RSA key', () => {
|
||||
let headers
|
||||
let client
|
||||
const queryUserPuplicKey = gql`
|
||||
query($queriedUserSlug: String) {
|
||||
User(slug: $queriedUserSlug) {
|
||||
id
|
||||
publicKey
|
||||
}
|
||||
}`
|
||||
const queryUserPrivateKey = gql`
|
||||
query($queriedUserSlug: String) {
|
||||
User(slug: $queriedUserSlug) {
|
||||
id
|
||||
privateKey
|
||||
}
|
||||
}`
|
||||
|
||||
const actionGenUserWithKeys = async () => {
|
||||
// Generate user with "privateKey" via 'CreateUser' mutation instead of using the factories "factory.create('User', {...})", see above.
|
||||
const variables = {
|
||||
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
||||
password: 'xYz',
|
||||
slug: 'apfel-strudel',
|
||||
name: 'Apfel Strudel',
|
||||
email: 'apfel-strudel@test.org'
|
||||
}
|
||||
await client.request(gql`
|
||||
mutation($id: ID, $password: String!, $slug: String, $name: String, $email: String) {
|
||||
CreateUser(id: $id, password: $password, slug: $slug, name: $name, email: $email) {
|
||||
id
|
||||
}
|
||||
}`, variables
|
||||
)
|
||||
}
|
||||
|
||||
// not authenticate
|
||||
beforeEach(async () => {
|
||||
client = new GraphQLClient(host)
|
||||
})
|
||||
|
||||
describe('unauthenticated query of "publicKey" (does the RSA key pair get generated at all?)', () => {
|
||||
it('returns publicKey', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await expect(
|
||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' })
|
||||
).toEqual(expect.objectContaining({
|
||||
User: [{
|
||||
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
||||
publicKey: expect.any(String)
|
||||
}]
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('unauthenticated query of "privateKey"', () => {
|
||||
it('throws "Not Authorised!"', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await expect(
|
||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' })
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
|
||||
// authenticate
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
describe('authenticated query of "publicKey"', () => {
|
||||
it('returns publicKey', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await expect(
|
||||
await client.request(queryUserPuplicKey, { queriedUserSlug: 'apfel-strudel' })
|
||||
).toEqual(expect.objectContaining({
|
||||
User: [{
|
||||
id: 'bcb2d923-f3af-479e-9f00-61b12e864667',
|
||||
publicKey: expect.any(String)
|
||||
}]
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated query of "privateKey"', () => {
|
||||
it('throws "Not Authorised!"', async () => {
|
||||
await actionGenUserWithKeys()
|
||||
await expect(
|
||||
client.request(queryUserPrivateKey, { queriedUserSlug: 'apfel-strudel' })
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
type Query {
|
||||
isLoggedIn: Boolean!
|
||||
"Get the currently logged in User based on the given JWT Token"
|
||||
# Get the currently logged in User based on the given JWT Token
|
||||
currentUser: User
|
||||
"Get the latest Network Statistics"
|
||||
# Get the latest Network Statistics
|
||||
statistics: Statistics!
|
||||
findPosts(filter: String!, limit: Int = 10): [Post]! @cypher(
|
||||
statement: """
|
||||
@ -18,7 +18,7 @@ type Query {
|
||||
)
|
||||
}
|
||||
type Mutation {
|
||||
"Get a JWT Token for the given Email and password"
|
||||
# Get a JWT Token for the given Email and password
|
||||
login(email: String!, password: String!): String!
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
changePassword(oldPassword:String!, newPassword: String!): String!
|
||||
@ -27,34 +27,14 @@ type Mutation {
|
||||
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})
|
||||
WHERE $type IN labels(n) AND NOT wu.id = $cypherParams.currentUserId
|
||||
MERGE (u)-[r:SHOUTED]->(n)
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
"Unshout the given Type and ID"
|
||||
unshout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (:User {id: $cypherParams.currentUserId})-[r:SHOUTED]->(n {id: $id})
|
||||
WHERE $type IN labels(n)
|
||||
DELETE r
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
"Follow the given Type and ID"
|
||||
follow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (n {id: $id}), (u:User {id: $cypherParams.currentUserId})
|
||||
WHERE $type IN labels(n) AND NOT $id = $cypherParams.currentUserId
|
||||
MERGE (u)-[r:FOLLOWS]->(n)
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
"Unfollow the given Type and ID"
|
||||
unfollow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (:User {id: $cypherParams.currentUserId})-[r:FOLLOWS]->(n {id: $id})
|
||||
WHERE $type IN labels(n)
|
||||
DELETE r
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
# Shout the given Type and ID
|
||||
shout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||
# Unshout the given Type and ID
|
||||
unshout(id: ID!, type: ShoutTypeEnum): Boolean!
|
||||
# Follow the given Type and ID
|
||||
follow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||
# Unfollow the given Type and ID
|
||||
unfollow(id: ID!, type: FollowTypeEnum): Boolean!
|
||||
}
|
||||
|
||||
type Statistics {
|
||||
@ -128,6 +108,7 @@ type User {
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
locationName: String
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT")
|
||||
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
@ -143,11 +124,13 @@ type User {
|
||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
"Is the currently logged in user following that user?"
|
||||
followedByCurrentUser: Boolean! @cypher(statement: """
|
||||
MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
""")
|
||||
# Is the currently logged in user following that user?
|
||||
followedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
|
||||
#contributions: [WrittenPost]!
|
||||
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
@ -155,11 +138,13 @@ type User {
|
||||
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
|
||||
# )
|
||||
contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
|
||||
contributionsCount: Int! @cypher(statement: """
|
||||
MATCH (this)-[:WROTE]->(r:Post)
|
||||
WHERE (NOT exists(r.deleted) OR r.deleted = false)
|
||||
AND (NOT exists(r.disabled) OR r.disabled = false)
|
||||
RETURN COUNT(r)"""
|
||||
contributionsCount: Int! @cypher(
|
||||
statement: """
|
||||
MATCH (this)-[:WROTE]->(r:Post)
|
||||
WHERE (NOT exists(r.deleted) OR r.deleted = false)
|
||||
AND (NOT exists(r.disabled) OR r.disabled = false)
|
||||
RETURN COUNT(r)
|
||||
"""
|
||||
)
|
||||
|
||||
comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
|
||||
@ -196,11 +181,13 @@ type Post {
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
relatedContributions: [Post]! @cypher(statement: """
|
||||
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||
RETURN DISTINCT post
|
||||
LIMIT 10
|
||||
""")
|
||||
relatedContributions: [Post]! @cypher(
|
||||
statement: """
|
||||
MATCH (this)-[:TAGGED|CATEGORIZED]->(categoryOrTag)<-[:TAGGED|CATEGORIZED]-(post:Post)
|
||||
RETURN DISTINCT post
|
||||
LIMIT 10
|
||||
"""
|
||||
)
|
||||
|
||||
tags: [Tag]! @relation(name: "TAGGED", direction: "OUT")
|
||||
categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT")
|
||||
@ -211,11 +198,13 @@ type Post {
|
||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
|
||||
"Has the currently logged in user shouted that post?"
|
||||
shoutedByCurrentUser: Boolean! @cypher(statement: """
|
||||
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
""")
|
||||
# Has the currently logged in user shouted that post?
|
||||
shoutedByCurrentUser: Boolean! @cypher(
|
||||
statement: """
|
||||
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
type Comment {
|
||||
@ -314,7 +303,15 @@ type Tag {
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
}
|
||||
|
||||
type SharedInboxEndpoint {
|
||||
id: ID!
|
||||
uri: String
|
||||
}
|
||||
|
||||
type SocialMedia {
|
||||
id: ID!
|
||||
url: String
|
||||
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
||||
}
|
||||
|
||||
|
||||
@ -28,10 +28,10 @@ let schema = makeAugmentedSchema({
|
||||
resolvers,
|
||||
config: {
|
||||
query: {
|
||||
exclude: ['Statistics', 'LoggedInUser']
|
||||
exclude: ['Notfication', 'Statistics', 'LoggedInUser']
|
||||
},
|
||||
mutation: {
|
||||
exclude: ['Statistics', 'LoggedInUser']
|
||||
exclude: ['Notfication', 'Statistics', 'LoggedInUser']
|
||||
},
|
||||
debug: debug
|
||||
}
|
||||
|
||||
2
backend/testing.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Unit Testing
|
||||
|
||||
@ -14,22 +14,22 @@
|
||||
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.6.tgz#022209e28a2b547dcde15b219f0c50f47aa5beb3"
|
||||
integrity sha512-lqK94b+caNtmKFs5oUVXlSpN3sm5IXZ+KfhMxOtr0LR2SqErzkoJilitjDvJ1WbjHlxLI7WtCjRmOLdOGJqtMQ==
|
||||
|
||||
"@babel/cli@~7.2.3":
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.2.3.tgz#1b262e42a3e959d28ab3d205ba2718e1923cfee6"
|
||||
integrity sha512-bfna97nmJV6nDJhXNPeEfxyMjWnt6+IjUAaDPiYRTBlm8L41n8nvw6UAqUCbvpFfU246gHPxW7sfWwqtF4FcYA==
|
||||
"@babel/cli@~7.4.3":
|
||||
version "7.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.4.3.tgz#353048551306ff42e5855b788b6ccd9477289774"
|
||||
integrity sha512-cbC5H9iTDV9H7sMxK5rUm18UbdVPNTPqgdzmQAkOUP3YLysgDWLZaysVAfylK49rgTlzL01a6tXyq9rCb3yLhQ==
|
||||
dependencies:
|
||||
commander "^2.8.1"
|
||||
convert-source-map "^1.1.0"
|
||||
fs-readdir-recursive "^1.1.0"
|
||||
glob "^7.0.0"
|
||||
lodash "^4.17.10"
|
||||
lodash "^4.17.11"
|
||||
mkdirp "^0.5.1"
|
||||
output-file-sync "^2.0.0"
|
||||
slash "^2.0.0"
|
||||
source-map "^0.5.0"
|
||||
optionalDependencies:
|
||||
chokidar "^2.0.3"
|
||||
chokidar "^2.0.4"
|
||||
|
||||
"@babel/code-frame@^7.0.0":
|
||||
version "7.0.0"
|
||||
@ -1104,6 +1104,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
||||
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
||||
|
||||
"@types/yup@0.26.12":
|
||||
version "0.26.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.12.tgz#60fc1a485923a929699d2107fac46e6769707c4a"
|
||||
integrity sha512-lWCsvLer6G84Gj7yh+oFGRuGHsqZd1Dwu47CVVL0ATw+bOnGDgMNHbTn80p1onT66fvLfN8FnRA3eRANsnnbbQ==
|
||||
|
||||
"@types/zen-observable@^0.5.3":
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.5.4.tgz#b863a4191e525206819e008097ebf0fb2e3a1cdc"
|
||||
@ -1604,6 +1609,14 @@ array-flatten@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||
|
||||
array-includes@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
|
||||
integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.7.0"
|
||||
|
||||
array-map@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
|
||||
@ -2144,10 +2157,10 @@ cheerio@~1.0.0-rc.3:
|
||||
lodash "^4.15.0"
|
||||
parse5 "^3.0.1"
|
||||
|
||||
chokidar@^2.0.3, chokidar@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.0.tgz#5fcb70d0b28ebe0867eb0f09d5f6a08f29a1efa0"
|
||||
integrity sha512-5t6G2SH8eO6lCvYOoUpaRnF5Qfd//gd7qJAkwRUw9qlGVkiQ13uwQngqbWWaurOsaAm9+kUGbITADxt6H0XFNQ==
|
||||
chokidar@^2.0.4, chokidar@^2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
|
||||
integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
|
||||
dependencies:
|
||||
anymatch "^2.0.0"
|
||||
async-each "^1.0.1"
|
||||
@ -2159,7 +2172,7 @@ chokidar@^2.0.3, chokidar@^2.1.0:
|
||||
normalize-path "^3.0.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
readdirp "^2.2.1"
|
||||
upath "^1.1.0"
|
||||
upath "^1.1.1"
|
||||
optionalDependencies:
|
||||
fsevents "^1.2.7"
|
||||
|
||||
@ -2887,7 +2900,7 @@ es-abstract@^1.4.3:
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.4"
|
||||
|
||||
es-abstract@^1.5.1:
|
||||
es-abstract@^1.5.1, es-abstract@^1.7.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
|
||||
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
|
||||
@ -2984,10 +2997,10 @@ eslint-import-resolver-node@^0.3.2:
|
||||
debug "^2.6.9"
|
||||
resolve "^1.5.0"
|
||||
|
||||
eslint-module-utils@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49"
|
||||
integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==
|
||||
eslint-module-utils@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
|
||||
integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
|
||||
dependencies:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^2.0.0"
|
||||
@ -3000,21 +3013,22 @@ eslint-plugin-es@^1.3.1:
|
||||
eslint-utils "^1.3.0"
|
||||
regexpp "^2.0.1"
|
||||
|
||||
eslint-plugin-import@~2.16.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f"
|
||||
integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==
|
||||
eslint-plugin-import@~2.17.1:
|
||||
version "2.17.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.1.tgz#b888feb4d9b3ee155113c8dccdd4bec5db33bdf4"
|
||||
integrity sha512-lzD9uvRvW4MsHzIOMJEDSb5MOV9LzgxRPBaovvOhJqzgxRHYfGy9QOrMuwHIh5ehKFJ7Z3DcrcGKDQ0IbP0EdQ==
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
contains-path "^0.1.0"
|
||||
debug "^2.6.9"
|
||||
doctrine "1.5.0"
|
||||
eslint-import-resolver-node "^0.3.2"
|
||||
eslint-module-utils "^2.3.0"
|
||||
eslint-module-utils "^2.4.0"
|
||||
has "^1.0.3"
|
||||
lodash "^4.17.11"
|
||||
minimatch "^3.0.4"
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.9.0"
|
||||
resolve "^1.10.0"
|
||||
|
||||
eslint-plugin-jest@~22.4.1:
|
||||
version "22.4.1"
|
||||
@ -3738,11 +3752,12 @@ graphql-request@~1.8.2:
|
||||
dependencies:
|
||||
cross-fetch "2.2.2"
|
||||
|
||||
graphql-shield@~5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.1.tgz#34cff4d1bfdcc3caa6fc348afb11503dde1893cd"
|
||||
integrity sha512-vVJ7rjkR7miWi/Zspr7/ibmtdL2gEHagCtpsJY534DyRE70r+PurCp2kR/e1fZhb4JdmTYCS+sokyYfH974/+w==
|
||||
graphql-shield@~5.3.3:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.3.tgz#e3fbdb2a5f927fe1bb660ccf60c614defcc3aed4"
|
||||
integrity sha512-9Hdmp71ewi9w7Tj1x8CSl3arWvtQOYKpZrsSBid2Vpr6BISAKe/2edEfgP4xYIKAkmpclG0Gl7ID5+qt1RJu7A==
|
||||
dependencies:
|
||||
"@types/yup" "0.26.12"
|
||||
lightercollective "^0.2.0"
|
||||
object-hash "^1.3.1"
|
||||
yup "^0.27.0"
|
||||
@ -5672,12 +5687,12 @@ node-releases@^1.1.13:
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
nodemon@~1.18.10:
|
||||
version "1.18.10"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.10.tgz#3ba63f64eb4c283cf3e4f75f30817e9d4f393afe"
|
||||
integrity sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ==
|
||||
nodemon@~1.18.11:
|
||||
version "1.18.11"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.11.tgz#d836ab663776e7995570b963da5bfc807e53f6b8"
|
||||
integrity sha512-KdN3tm1zkarlqNo4+W9raU3ihM4H15MVMSE/f9rYDZmFgDHAfAJsomYrHhApAkuUemYjFyEeXlpCOQ2v5gtBEw==
|
||||
dependencies:
|
||||
chokidar "^2.1.0"
|
||||
chokidar "^2.1.5"
|
||||
debug "^3.1.0"
|
||||
ignore-by-default "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
@ -6671,7 +6686,7 @@ resolve@1.1.7:
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
|
||||
|
||||
resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
|
||||
resolve@^1.10.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.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==
|
||||
@ -7638,10 +7653,10 @@ unzip-response@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
|
||||
integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
|
||||
|
||||
upath@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
|
||||
integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
|
||||
upath@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
|
||||
integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
|
||||
|
||||
update-notifier@^2.5.0:
|
||||
version "2.5.0"
|
||||
|
||||
37
cypress/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# End-to-End Testing
|
||||
|
||||
## Run Tests
|
||||
|
||||
To run the tests, make sure you are at the root level of the project, in your console and run the following command:
|
||||
|
||||
```bash
|
||||
$ yarn cypress:setup
|
||||
```
|
||||
|
||||
After verifying that there are no errors with the servers starting, open another tab in your terminal and run the following command:
|
||||
|
||||
```bash
|
||||
$ yarn cypress:run
|
||||
```
|
||||
|
||||

|
||||
|
||||
After the test runs, you will also get some video footage of the test run which you can then analyse in more detail.
|
||||
|
||||
## Open Interactive Test Console
|
||||
|
||||
If you are like me, you might want to see some visual output. The interactive cypress environment also helps at debugging your tests, you can even time travel between individual steps and see the exact state of the app.
|
||||
|
||||
To use this feature, you will still run the `yarn cypress:setup` above, but instead of `yarn cypress:run` open another tab in your terminal and run the following command:
|
||||
|
||||
```bash
|
||||
$ yarn cypress:open
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Write some Tests
|
||||
|
||||
Check out the Cypress documentation for further information on how to write tests:
|
||||
[https://docs.cypress.io/guides/getting-started/writing-your-first-test.html\#Write-a-simple-test](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Write-a-simple-test)
|
||||
|
||||
272
cypress/features.md
Normal file
@ -0,0 +1,272 @@
|
||||
# Network Specification
|
||||
|
||||
Human Connection is a nonprofit social, action and knowledge network that connects information to action and promotes positive local and global change in all areas of life.
|
||||
|
||||
* **Social**: Interact with other people not just by commenting their posts, but by providing **Pro & Contra** arguments, give a **Versus** or ask them by integrated **Chat** or **Let's Talk**
|
||||
* **Knowledge**: Read articles about interesting topics and find related posts in the **More Info** tab or by **Filtering** based on **Categories** and **Tagging** or by using the **Fulltext Search**.
|
||||
* **Action**: Don't just read about how to make the world a better place, but come into **Action** by following provided suggestions on the **Action** tab provided by other people or **Organisations**.
|
||||
|
||||
## Features
|
||||
|
||||
The following features will be implemented. This gets done in three steps:
|
||||
|
||||
1. First we will implement a basic feature set and provide a test system to test the basic network functionality.
|
||||
2. In a second step we will make our prototype publicly available with an advanced feature set including the technology and organizational structure to drive a bigger public social network.
|
||||
3. In a third step all the remaining features will be implemented to build the full product.
|
||||
|
||||
### User Account
|
||||
|
||||
[Cucumber Features](./integration/user_account)
|
||||
|
||||
* Sign-up
|
||||
* Agree to Data Privacy Statement
|
||||
* Agree to Terms of Service
|
||||
* Login
|
||||
* Logoff
|
||||
* Change User Name
|
||||
* Change Email Address
|
||||
* Change Password
|
||||
* Delete Account
|
||||
* Download User's Content
|
||||
* GDPR-Information about stored Content
|
||||
* Choosing Interface Language \(e.g. German / English / French\)
|
||||
* Persistent Links
|
||||
|
||||
### User Profile
|
||||
|
||||
[Cucumber Features](./integration/user_profile)
|
||||
|
||||
* Upload and Change Avatar
|
||||
* Upload and Change Profile Picture
|
||||
* Edit Social Media Accounts
|
||||
* Edit Locale information
|
||||
* Show and delete Bookmarks \(later\)
|
||||
* Show Posts of a specific User
|
||||
* Show Comments of a specific User
|
||||
|
||||
### Dashboard
|
||||
|
||||
[Clickdummy](https://preview.uxpin.com/24a2ab8adcd84f9a763d87ed27251351225e0ecd#/pages/99768919/simulate/sitemap?mode=i)
|
||||
|
||||
* Show Link to own Profile
|
||||
* Show Friends Widget
|
||||
* Show Favorites Widget
|
||||
* Show Get Friends Widget
|
||||
* Show popular Hashtags Widget
|
||||
* Show Mini-Statistics Widget \(all time\)
|
||||
* Show Chatrooms Widget
|
||||
* Show List of Let's Talk requests with online status of requesting people
|
||||
|
||||
### Posts
|
||||
|
||||
[Cucumber Features](./integration/post/)
|
||||
|
||||
* Creating Posts
|
||||
* Persistent Links
|
||||
* Upload Teaser Picture for Post
|
||||
* Upload additional Pictures
|
||||
* Editing Title and Content
|
||||
* Allow embedded Conten \(Videos, Sound, ...\)
|
||||
* Choosing a Category
|
||||
* Adding Tags
|
||||
* Choosing Language \(e.g. German / English / French\)
|
||||
* Choosing Visibility \(Public / Friends / Private\)
|
||||
* Shout Button for Posts
|
||||
* Bookmark Posts \(later\)
|
||||
* Optionally provide Let's Talk Feature
|
||||
* Optionally provide Commenting Feature
|
||||
|
||||
### Comments
|
||||
|
||||
* Creating Comments
|
||||
* Deleting Comments
|
||||
* Editing Comments
|
||||
* Upvote comments of others
|
||||
|
||||
### Contribution List
|
||||
|
||||
* Show Posts by Tiles
|
||||
* Show Posts as List
|
||||
* Filter by Category \(Health and Wellbeing, Global Peace & Non-Violence, ...\)
|
||||
* Filter by Mood \(Funny, Happy, Surprised, Cry, Angry, ...\)
|
||||
* Filter by Source \(Connections, Following, Individuals, Non-Profits, ...\)
|
||||
* Filter by Posts & Tools \(Post, Events, CanDos, ...\)
|
||||
* Filter by Format Type \(Text, Pictures, Video, ...\)
|
||||
* Extended Filter \(Continent, Country, Language, ...\)
|
||||
* Sort Posts by Date
|
||||
* Sort Posts by Shouts
|
||||
* Sort Posts by most Comments
|
||||
* Sort Posts by Emoji-Count \(all Types\)
|
||||
|
||||
### Blacklist
|
||||
|
||||
[Video](https://www.youtube.com/watch?v=-uDvvmN8hLQ)
|
||||
|
||||
* Blacklist Users
|
||||
* Blacklist specific Terms
|
||||
* Blacklist Tags
|
||||
* Switch on/off Adult Content
|
||||
|
||||
### Search
|
||||
|
||||
[Cucumber Features](./integration/search)
|
||||
|
||||
* Search for Categories
|
||||
* Search for Tags
|
||||
* Fulltext Search
|
||||
|
||||
### CanDos
|
||||
|
||||
* Creating CanDos
|
||||
* Editing Title and Content
|
||||
* Choosing a Category
|
||||
* Adding Tags
|
||||
* Choosing Language \(e.g. German / English / French\)
|
||||
* Choosing Visibility \(Public / Friends / Private\)
|
||||
* Choosing Difficulty
|
||||
* Editing Why - why should you do this
|
||||
* Editing Usefulness - what is it good for
|
||||
|
||||
### Versus \(interaction on existing Post\)
|
||||
|
||||
* Create / edit / delete Versus
|
||||
|
||||
### Jobs
|
||||
|
||||
* Create, edit and delete Jobs by an User
|
||||
* Handle Jobs as Part of Projects
|
||||
* Handle Jobs done by Organizations
|
||||
|
||||
### Projects
|
||||
|
||||
* Create, edit and delete Projects
|
||||
* Edit Title and Description for the Project
|
||||
* Set Project Type
|
||||
* Set and Edit Timeline for the Project
|
||||
* Add Media to the Project
|
||||
* Chat about the Project
|
||||
|
||||
### Pro & Contra
|
||||
|
||||
* Create Pro and Con \(2-row\)
|
||||
* Add Arguments on Pro or Con Side
|
||||
* Rate up Arguments
|
||||
* Add Tags
|
||||
* Attach Media
|
||||
|
||||
### Votes
|
||||
|
||||
* Create Votes \(Surveys with two or more Choices\)
|
||||
* Add Title and Description
|
||||
* Let Users vote
|
||||
* Add Tags
|
||||
|
||||
### Bestlist
|
||||
|
||||
* Create Bestlist
|
||||
* Create Votes \(Surveys\)
|
||||
* Add Title and Description
|
||||
* Add Tags
|
||||
* Let Users vote for Best Item
|
||||
* Set Settings \(allow Uploads, allow Links, ...\)
|
||||
|
||||
### Events
|
||||
|
||||
* Create Events
|
||||
* Add Title and Description
|
||||
* Choose Date and Location
|
||||
* Add Tags
|
||||
|
||||
### More Info
|
||||
|
||||
Shows autmatically releated information for existing post.
|
||||
|
||||
* Show related Posts
|
||||
* Show Pros and Cons
|
||||
* Show Bestlist
|
||||
* Show Votes
|
||||
* Link to corresponding Chatroom
|
||||
|
||||
### Take Action
|
||||
|
||||
Shows automatically related actions for existing post.
|
||||
|
||||
* Show related Organisations
|
||||
* Show related CanDos
|
||||
* Show related Projects
|
||||
* Show related Jobs
|
||||
* Show related Events
|
||||
* Show Map
|
||||
|
||||
### Badges System
|
||||
|
||||
* Importing Badge Information \(CSV\)
|
||||
* Showing Badges
|
||||
* Badge Administration by Admins
|
||||
* Choosing Badges to display by User
|
||||
|
||||
### Chat
|
||||
|
||||
* Basic 1:1 Chat functionality
|
||||
|
||||
### Let's Talk
|
||||
|
||||
* Request Let's talk with Author of Post
|
||||
* Requestor can request private or public Let's Talk
|
||||
* Requestor can choose the Chat format \(Video, Audio, Text\)
|
||||
* Interact with interested People 1:1
|
||||
* Approve request from Requestor
|
||||
|
||||
### Organizations
|
||||
|
||||
* Propose Organizations by users
|
||||
* Set Name and Details
|
||||
* Set Homepage
|
||||
* Set Region
|
||||
* Set Topic
|
||||
* Commit organizations by HC-Org-Team
|
||||
* Panel for Organisation Handling by themselfes
|
||||
* Choose/Mark Users as authorized to manage an Organization
|
||||
|
||||
### Moderation
|
||||
|
||||
[Cucumber Features](./integration/moderation)
|
||||
|
||||
* Report Button for users for doubtful Content
|
||||
* Moderator Panel
|
||||
* List of reported Content \(later replaced by User-Moderation\)
|
||||
* Mark verified Users as Moderators
|
||||
* Show Posts to be moderated highlighted to User-Moderators
|
||||
* Statistics about kinds of reported Content by Time
|
||||
* Statistics about Decisions in Moderation
|
||||
|
||||
### 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
|
||||
|
||||
### Invitation
|
||||
|
||||
* Allow Users to invite others by Email
|
||||
* Allow Users to register with Invite Code
|
||||
* Double-opt-in by Email
|
||||
|
||||
### Internationalization
|
||||
|
||||
[Cucumber Features](./integration/internationalization)
|
||||
|
||||
* Frontend UI
|
||||
* Backend Error Messages
|
||||
|
||||
### Federation
|
||||
|
||||
* Provide Server-Server ActivityPub-API
|
||||
* Provide User-Server Activitypub-API
|
||||
* Receiving public addressed Article and Note Objects
|
||||
* Receiving Like and Follow Activities
|
||||
* Receiving Undo and Delete Activities for Articles and Notes
|
||||
* Serving Webfinger records and Actor Objects
|
||||
* Serving Followers, Following and Outbox collections
|
||||
|
||||
@ -55,7 +55,7 @@ When(
|
||||
cy.contains('.ds-card', davidIrvingName)
|
||||
.find('.content-menu-trigger')
|
||||
.first()
|
||||
.click()
|
||||
.click({force: true})
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
.contains('Report User')
|
||||
|
||||
@ -61,3 +61,52 @@ Then(
|
||||
'I can see my new name {string} when I click on my profile picture in the top right',
|
||||
name => matchNameInUserMenu(name)
|
||||
)
|
||||
|
||||
When('I click on the {string} link', link => {
|
||||
cy.get('a')
|
||||
.contains(link)
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I should be on the {string} page', page => {
|
||||
cy.location()
|
||||
.should(loc => {
|
||||
expect(loc.pathname).to.eq(page)
|
||||
})
|
||||
.get('h3')
|
||||
.should('contain', 'Social media')
|
||||
})
|
||||
|
||||
Then('I add a social media link', () => {
|
||||
cy.get("input[name='social-media']")
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('it gets saved successfully', () => {
|
||||
cy.get('.iziToast-message')
|
||||
.should('contain', 'Updated user')
|
||||
})
|
||||
|
||||
Then('the new social media link shows up on the page', () => {
|
||||
cy.get('a[href="https://freeradical.zone/peter-pan"]')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
Given('I have added a social media link', () => {
|
||||
cy.openPage('/settings/my-social-media')
|
||||
.get("input[name='social-media']")
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('they should be able to see my social media links', () => {
|
||||
cy.get('.ds-card-content')
|
||||
.contains('Where else can I find Peter Pan?')
|
||||
.get('a[href="https://freeradical.zone/peter-pan"]')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
@ -228,7 +228,7 @@ Then('I get redirected to {string}', route => {
|
||||
})
|
||||
|
||||
Then('the post was saved successfully', () => {
|
||||
cy.get('.ds-card-header > .ds-heading').should('contain', lastPost.title)
|
||||
cy.get('.ds-card-content > .ds-heading').should('contain', lastPost.title)
|
||||
cy.get('.content').should('contain', lastPost.content)
|
||||
})
|
||||
|
||||
|
||||
21
cypress/integration/user_profile/SocialMedia.feature
Normal file
@ -0,0 +1,21 @@
|
||||
Feature: List Social Media Accounts
|
||||
As a User
|
||||
I'd like to enter my social media
|
||||
So I can show them to other users to get in contact
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
And I am logged in
|
||||
|
||||
Scenario: Adding Social Media
|
||||
Given I am on the "settings" page
|
||||
And I click on the "Social media" link
|
||||
Then I should be on the "/settings/my-social-media" page
|
||||
When I add a social media link
|
||||
Then it gets saved successfully
|
||||
And the new social media link shows up on the page
|
||||
|
||||
Scenario: Other user's viewing my Social Media
|
||||
Given I have added a social media link
|
||||
When people visit my profile page
|
||||
Then they should be able to see my social media links
|
||||
@ -1,34 +1,29 @@
|
||||
# Human-Connection Nitro | Deployment Configuration
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Deployment)
|
||||
# Human-Connection Nitro \| Deployment Configuration
|
||||
|
||||
Todos:
|
||||
- [x] check labels and selectors if they all are correct
|
||||
- [x] configure NGINX from yml
|
||||
- [x] configure Let's Encrypt cert-manager from yml
|
||||
- [x] configure ingress from yml
|
||||
- [x] configure persistent & shared storage between nodes
|
||||
- [x] reproduce setup locally
|
||||
We deploy with [kubernetes](https://kubernetes.io/). In order to deploy your own
|
||||
network you have to [install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
|
||||
and get a kubernetes cluster.
|
||||
|
||||
We have tested two different kubernetes providers: [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/)
|
||||
and [Digital Ocean](https://www.digitalocean.com/).
|
||||
|
||||
## Minikube
|
||||
There are many Kubernetes distributions, but if you're just getting started,
|
||||
Minikube is a tool that you can use to get your feet wet.
|
||||
|
||||
There are many Kubernetes providers, but if you're just getting started, Minikube is a tool that you can use to get your feet wet.
|
||||
|
||||
[Install Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/)
|
||||
|
||||
Open minikube dashboard:
|
||||
```
|
||||
|
||||
```text
|
||||
$ minikube dashboard
|
||||
```
|
||||
This will give you an overview.
|
||||
Some of the steps below need some timing to make ressources available to other
|
||||
dependent deployments. Keeping an eye on the dashboard is a great way to check
|
||||
that.
|
||||
|
||||
Follow the [installation instruction](#installation-with-kubernetes) below.
|
||||
If all the pods and services have settled and everything looks green in your
|
||||
minikube dashboard, expose the `nitro-web` service on your host system with:
|
||||
This will give you an overview. Some of the steps below need some timing to make ressources available to other dependent deployments. Keeping an eye on the dashboard is a great way to check that.
|
||||
|
||||
```shell
|
||||
Follow the [installation instruction](deployment.md#installation-with-kubernetes) below. If all the pods and services have settled and everything looks green in your minikube dashboard, expose the `nitro-web` service on your host system with:
|
||||
|
||||
```text
|
||||
$ minikube service nitro-web --namespace=human-connection
|
||||
```
|
||||
|
||||
@ -36,9 +31,9 @@ $ minikube service nitro-web --namespace=human-connection
|
||||
|
||||
1. At first, create a cluster on Digital Ocean.
|
||||
2. Download the config.yaml if the process has finished.
|
||||
3. Put the config file where you can find it later (preferable in your home directory under `~/.kube/`)
|
||||
3. Put the config file where you can find it later \(preferable in your home directory under `~/.kube/`\)
|
||||
4. In the open terminal you can set the current config for the active session: `export KUBECONFIG=~/.kube/THE-NAME-OF-YOUR-CLUSTER-kubeconfig.yaml`. You could make this change permanent by adding the line to your `.bashrc` or `~/.config/fish/config.fish` depending on your shell.
|
||||
|
||||
|
||||
Otherwise you would have to always add `--kubeconfig ~/.kube/THE-NAME-OF-YOUR-CLUSTER-kubeconfig.yaml` on every `kubectl` command that you are running.
|
||||
|
||||
5. Now check if you can connect to the cluster and if its your newly created one by running: `kubectl get nodes`
|
||||
@ -46,17 +41,21 @@ $ minikube service nitro-web --namespace=human-connection
|
||||
If you got the steps right above and see your nodes you can continue.
|
||||
|
||||
First, install kubernetes dashboard:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f dashboard/
|
||||
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml
|
||||
|
||||
```
|
||||
|
||||
Get your token on the command line:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
|
||||
```
|
||||
|
||||
It should print something like:
|
||||
```
|
||||
|
||||
```text
|
||||
Name: admin-user-token-6gl6l
|
||||
Namespace: kube-system
|
||||
Labels: <none>
|
||||
@ -70,60 +69,59 @@ Data
|
||||
ca.crt: 1025 bytes
|
||||
namespace: 11 bytes
|
||||
token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLTZnbDZsIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMTZhZmJhOS1kZmVjLTExZTctYmJiOS05MDFiMGU1MzI1MTYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.M70CU3lbu3PP4OjhFms8PVL5pQKj-jj4RNSLA4YmQfTXpPUuxqXjiTf094_Rzr0fgN_IVX6gC4fiNUL5ynx9KU-lkPfk0HnX8scxfJNzypL039mpGt0bbe1IXKSIRaq_9VW59Xz-yBUhycYcKPO9RM2Qa1Ax29nqNVko4vLn1_1wPqJ6XSq3GYI8anTzV8Fku4jasUwjrws6Cn6_sPEGmL54sq5R4Z5afUtv-mItTmqZZdxnkRqcJLlg2Y8WbCPogErbsaCDJoABQ7ppaqHetwfM_0yMun6ABOQbIwwl8pspJhpplKwyo700OSpvTT9zlBsu-b35lzXGBRHzv5g_RA
|
||||
|
||||
```
|
||||
|
||||
Proxy localhost to the remote kubernetes dashboard:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
$ kubectl proxy
|
||||
```
|
||||
|
||||
Grab the token from above and paste it into the login screen at [http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/)
|
||||
|
||||
|
||||
## Installation with kubernetes
|
||||
|
||||
You have to do some prerequisites e.g. change some secrets according to your
|
||||
own setup.
|
||||
You have to do some prerequisites e.g. change some secrets according to your own setup.
|
||||
|
||||
### Edit secrets
|
||||
|
||||
```sh
|
||||
```bash
|
||||
$ cp secrets.template.yaml human-connection/secrets.yaml
|
||||
```
|
||||
|
||||
Change all secrets as needed.
|
||||
|
||||
If you want to edit secrets, you have to `base64` encode them. See [kubernetes
|
||||
documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually).
|
||||
```shell
|
||||
If you want to edit secrets, you have to `base64` encode them. See [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret-manually).
|
||||
|
||||
```text
|
||||
# example how to base64 a string:
|
||||
$ echo -n 'admin' | base64
|
||||
YWRtaW4=
|
||||
```
|
||||
|
||||
Those secrets get `base64` decoded in a kubernetes pod.
|
||||
|
||||
### Create a namespace
|
||||
```shell
|
||||
|
||||
```text
|
||||
$ kubectl apply -f namespace-human-connection.yaml
|
||||
```
|
||||
|
||||
Switch to the namespace `human-connection` in your kubernetes dashboard.
|
||||
|
||||
|
||||
### Run the configuration
|
||||
```shell
|
||||
|
||||
```text
|
||||
$ kubectl apply -f human-connection/
|
||||
```
|
||||
|
||||
This can take a while because kubernetes will download the docker images.
|
||||
Sit back and relax and have a look into your kubernetes dashboard.
|
||||
Wait until all pods turn green and they don't show a warning
|
||||
`Waiting: ContainerCreating` anymore.
|
||||
This can take a while because kubernetes will download the docker images. Sit back and relax and have a look into your kubernetes dashboard. Wait until all pods turn green and they don't show a warning `Waiting: ContainerCreating` anymore.
|
||||
|
||||
#### Setup Ingress and HTTPS
|
||||
|
||||
Follow [this quick start guide](https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html)
|
||||
and install certmanager via helm and tiller:
|
||||
```
|
||||
Follow [this quick start guide](https://docs.cert-manager.io/en/latest/tutorials/acme/quick-start/index.html) and install certmanager via helm and tiller:
|
||||
|
||||
```text
|
||||
$ kubectl create serviceaccount tiller --namespace=kube-system
|
||||
$ kubectl create clusterrolebinding tiller-admin --serviceaccount=kube-system:tiller --clusterrole=cluster-admin
|
||||
$ helm init --service-account=tiller
|
||||
@ -133,53 +131,56 @@ $ kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/relea
|
||||
$ helm install --name cert-manager --namespace cert-manager stable/cert-manager
|
||||
```
|
||||
|
||||
Create letsencrypt issuers. *Change the email address* in these files before
|
||||
running this command.
|
||||
```sh
|
||||
Create letsencrypt issuers. _Change the email address_ in these files before running this command.
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f human-connection/https/
|
||||
```
|
||||
Create an ingress service in namespace `human-connection`. *Change the domain
|
||||
name* according to your needs:
|
||||
```sh
|
||||
|
||||
Create an ingress service in namespace `human-connection`. _Change the domain name_ according to your needs:
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f human-connection/ingress/
|
||||
```
|
||||
|
||||
Check the ingress server is working correctly:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
$ curl -kivL -H 'Host: <DOMAIN_NAME>' 'https://<IP_ADDRESS>'
|
||||
```
|
||||
If the response looks good, configure your domain registrar for the new IP
|
||||
address and the domain.
|
||||
|
||||
Now let's get a valid HTTPS certificate. According to the tutorial above, check
|
||||
your tls certificate for staging:
|
||||
```sh
|
||||
If the response looks good, configure your domain registrar for the new IP address and the domain.
|
||||
|
||||
Now let's get a valid HTTPS certificate. According to the tutorial above, check your tls certificate for staging:
|
||||
|
||||
```bash
|
||||
$ kubectl describe --namespace=human-connection certificate tls
|
||||
$ kubectl describe --namespace=human-connection secret tls
|
||||
```
|
||||
|
||||
If everything looks good, update the issuer of your ingress. Change the
|
||||
annotation `certmanager.k8s.io/issuer` from `letsencrypt-staging` to
|
||||
`letsencrypt-prod` in your ingress configuration in
|
||||
`human-connection/ingress/ingress.yaml`.
|
||||
If everything looks good, update the issuer of your ingress. Change the annotation `certmanager.k8s.io/issuer` from `letsencrypt-staging` to `letsencrypt-prod` in your ingress configuration in `human-connection/ingress/ingress.yaml`.
|
||||
|
||||
```sh
|
||||
```bash
|
||||
$ kubectl apply -f human-connection/ingress/ingress.yaml
|
||||
```
|
||||
|
||||
Delete the former secret to force a refresh:
|
||||
```
|
||||
|
||||
```text
|
||||
$ kubectl --namespace=human-connection delete secret tls
|
||||
```
|
||||
|
||||
Now, HTTPS should be configured on your domain. Congrats.
|
||||
|
||||
#### Legacy data migration
|
||||
|
||||
This setup is completely optional and only required if you have data on a server
|
||||
which is running our legacy code and you want to import that data. It will
|
||||
import the uploads folder and migrate a dump of mongodb into neo4j.
|
||||
This setup is completely optional and only required if you have data on a server which is running our legacy code and you want to import that data. It will import the uploads folder and migrate a dump of mongodb into neo4j.
|
||||
|
||||
**Prepare migration of Human Connection legacy server**
|
||||
|
||||
##### Prepare migration of Human Connection legacy server
|
||||
Create a configmap with the specific connection data of your legacy server:
|
||||
```sh
|
||||
|
||||
```bash
|
||||
$ kubectl create configmap db-migration-worker \
|
||||
--namespace=human-connection \
|
||||
--from-literal=SSH_USERNAME=someuser \
|
||||
@ -192,13 +193,9 @@ $ kubectl create configmap db-migration-worker \
|
||||
--from-literal=NEO4J_URI=bolt://localhost:7687
|
||||
```
|
||||
|
||||
Create a secret with your public and private ssh keys. As the
|
||||
[kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys)
|
||||
points out, you should be careful with your ssh keys. Anyone with access to your
|
||||
cluster will have access to your ssh keys. Better create a new pair with
|
||||
`ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`:
|
||||
Create a secret with your public and private ssh keys. As the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-pod-with-ssh-keys) points out, you should be careful with your ssh keys. Anyone with access to your cluster will have access to your ssh keys. Better create a new pair with `ssh-keygen` and copy the public key to your legacy server with `ssh-copy-id`:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
$ kubectl create secret generic ssh-keys \
|
||||
--namespace=human-connection \
|
||||
--from-file=id_rsa=/path/to/.ssh/id_rsa \
|
||||
@ -206,8 +203,10 @@ $ kubectl create secret generic ssh-keys \
|
||||
--from-file=known_hosts=/path/to/.ssh/known_hosts
|
||||
```
|
||||
|
||||
##### Migrate legacy database
|
||||
**Migrate legacy database**
|
||||
|
||||
Patch the existing deployments to use a multi-container setup:
|
||||
|
||||
```bash
|
||||
cd legacy-migration
|
||||
kubectl apply -f volume-claim-mongo-export.yaml
|
||||
@ -217,9 +216,11 @@ cd ..
|
||||
```
|
||||
|
||||
Run the migration:
|
||||
```shell
|
||||
|
||||
```text
|
||||
$ kubectl --namespace=human-connection get pods
|
||||
# change <POD_IDs> below
|
||||
$ kubectl --namespace=human-connection exec -it nitro-neo4j-65bbdb597c-nc2lv migrate
|
||||
$ kubectl --namespace=human-connection exec -it nitro-backend-c6cc5ff69-8h96z sync_uploads
|
||||
```
|
||||
|
||||
|
||||
120
edit-this-documentation.md
Normal file
@ -0,0 +1,120 @@
|
||||
# Edit this Documentation
|
||||
|
||||
Go to the section and theme you want to change: On the left navigator.
|
||||
|
||||
Click **Edit on GitHub** on the right.
|
||||
|
||||
On the **Issue** tab you’ll find the open issues. Read what need to be done by clicking on the issue you like to fix.
|
||||
|
||||
By going backwards in the browser **\(!\)**, again go to the **Code** tab.
|
||||
|
||||
Click on the **edit pencil** on the right side directly above the text to edit this file on your fork of Human Connection \(HC\).
|
||||
|
||||
You can see a preview of your changes by clicking the **Preview changes** tab aside the **Edit file** tab.
|
||||
|
||||
If you are ready, fill in the **Propose file change** at the end of the webpage.
|
||||
|
||||
After that you have to send your change to the HC basis with a pull request. Here make a comment which issue you have fixed. At least the number.
|
||||
|
||||
## Markdown your documentation
|
||||
|
||||
To design your documentation see the syntax description at GitBook:
|
||||
|
||||
[https://toolchain.gitbook.com/syntax/markdown.html](https://toolchain.gitbook.com/syntax/markdown.html)
|
||||
|
||||
### Some quick Examples
|
||||
|
||||
#### Headlines
|
||||
|
||||
```text
|
||||
# Main headline
|
||||
## Smaller headlines
|
||||
### Small headlines
|
||||
```
|
||||
|
||||
#### Tabs
|
||||
|
||||
```text
|
||||
{% tabs %}
|
||||
{% tab title="XXX" %}
|
||||
XXX
|
||||
{% endtab %}
|
||||
{% tab title="XXX" %}
|
||||
XXX
|
||||
{% endtab %}
|
||||
…
|
||||
{% endtabs %}
|
||||
```
|
||||
|
||||
#### Commands
|
||||
|
||||
```text
|
||||
```LANGUAGE (for text highlighting)
|
||||
XXX
|
||||
```
|
||||
|
||||
```text
|
||||
#### Links
|
||||
|
||||
```text
|
||||
[https://XXX](XXX)
|
||||
```
|
||||
|
||||
#### Screenshots or other Images
|
||||
|
||||
```text
|
||||

|
||||
```
|
||||
|
||||
#### Hints for ToDos
|
||||
|
||||
```text
|
||||
{% hint style="info" %} TODO: XXX {% endhint %}
|
||||
```
|
||||
|
||||
## Host the screenshots
|
||||
|
||||
### Host on Human Connection
|
||||
|
||||
{% hint style="info" %}
|
||||
TODO: How to host on Human Connection \(GitHub\) ...
|
||||
{% endhint %}
|
||||
|
||||
### Quick Solution
|
||||
|
||||
To quickly host the screenshots go to:
|
||||
|
||||
[https://imgur.com](https://imgur.com).
|
||||
|
||||
There click the green button **New post**.
|
||||
|
||||
Drag the image into the appropriate area.
|
||||
|
||||
Right click on it and choose kind of **Open link in new tab**.
|
||||
|
||||
Copy the URL and paste it were you need it.
|
||||
|
||||
## Screenshot modification
|
||||
|
||||
### Add an arrow or some other marking stuff
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="macOS" %}
|
||||
#### In the Preview App
|
||||
|
||||
Got to: **Menu** + **Tools** \(GER: Werkzeuge\) + **Annotate** \(GER: Anmerkungen\) + etc.
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Windows" %}
|
||||
{% hint style="info" %}
|
||||
TODO: How to modify screenshots in Windows ...
|
||||
{% endhint %}
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Linux" %}
|
||||
{% hint style="info" %}
|
||||
TODO: How to modify screenshots in Linux ...
|
||||
{% endhint %}
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
166
installation.md
Normal file
@ -0,0 +1,166 @@
|
||||
# Installation
|
||||
|
||||
## General Install Instructions
|
||||
|
||||
The repository can be found on GitHub. [https://github.com/Human-Connection/Human-Connection](https://github.com/Human-Connection/Human-Connection)
|
||||
|
||||
{% hint style="info" %}
|
||||
TODO: Create documentation section for How to Start and Beginners.
|
||||
{% endhint %}
|
||||
|
||||
Here are some general informations about our [GitHub Standard Fork & Pull Request Workflow](https://gist.github.com/Chaser324/ce0505fbed06b947d962).
|
||||
|
||||
#### Fork the Repository
|
||||
|
||||
Click on the fork button.
|
||||
|
||||

|
||||
|
||||
#### Clone your new Repository
|
||||
|
||||
Set the current working folder to the path in which the repository should be cloned \(copied\).
|
||||
|
||||
```bash
|
||||
$ cd PATH-FOR-REPO
|
||||
```
|
||||
|
||||
For cloning your new repository to your local machine modify the following command to add your GitHub user name.
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="HTTPS" %}
|
||||
```bash
|
||||
$ git clone https://github.com/YOUR-GITHUB-USERNAME/Human-Connection.git
|
||||
```
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="SSH" %}
|
||||
```bash
|
||||
$ git clone git@github.com:YOUR-GITHUB-USERNAME/Human-Connection.git
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
Change into the new folder.
|
||||
|
||||
```bash
|
||||
$ cd Human-Connection
|
||||
```
|
||||
|
||||
Add the original Human Connection repository as `upstream`. This prepares you to synchronize your local clone with a simple pull command in the future.
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="HTTPS" %}
|
||||
```bash
|
||||
$ git remote add upstream https://github.com/Human-Connection/Human-Connection.git
|
||||
```
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="SSH" %}
|
||||
```bash
|
||||
$ git remote add upstream git@github.com:Human-Connection/Human-Connection.git
|
||||
```
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
## Docker Installation
|
||||
|
||||
Docker is a software development container tool that combines software and its dependencies into one standardized unit that contains everything needed to run it. This helps us to avoid problems with dependencies and makes installation easier.
|
||||
|
||||
### General Installation of Docker
|
||||
|
||||
There are [sevaral ways to install Docker CE](https://docs.docker.com/install/) on your computer or server.
|
||||
|
||||
{% tabs %}
|
||||
{% tab title="Docker Desktop macOS" %}
|
||||
Follow these instructions to [install Docker Desktop on macOS](https://docs.docker.com/docker-for-mac/install/).
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Docker Desktop Windows" %}
|
||||
Follow these instructions to [install Docker Desktop on Windows](https://docs.docker.com/docker-for-windows/install/).
|
||||
{% endtab %}
|
||||
|
||||
{% tab title="Docker CE" %}
|
||||
Follow these instructions to [install Docker CE](https://docs.docker.com/install/).
|
||||
|
||||
This is a great option for Linux users.
|
||||
{% endtab %}
|
||||
{% endtabs %}
|
||||
|
||||
Check the correct Docker installation by checking the version before proceeding. E.g. we have the following versions:
|
||||
|
||||
```bash
|
||||
$ docker --version
|
||||
Docker version 18.09.2
|
||||
$ docker-compose --version
|
||||
docker-compose version 1.23.2
|
||||
```
|
||||
|
||||
### Install Nitro with Docker
|
||||
|
||||
Run the following command to install Nitro as a Docker container. This installation includes Neo4j.
|
||||
|
||||
The installation takes a bit longer on the first pass or on rebuild ...
|
||||
|
||||
```bash
|
||||
$ docker-compose up
|
||||
|
||||
# rebuild the containers for a cleanup
|
||||
$ docker-compose up --build
|
||||
```
|
||||
|
||||
#### Seed Neo4j in Docker
|
||||
|
||||
To seed the Neo4j database with default data, that GraphQL requests or playing with our GraphQL Playground returns anything else than an empty response, run the command.
|
||||
|
||||
Run the following command to seed the Neo4j database with default data requested by Nitro-Web through GraphQL or when you play with our GraphQL playground.
|
||||
|
||||
```bash
|
||||
# open another terminal
|
||||
|
||||
# create indices etc.
|
||||
$ docker-compose exec neo4j migrate
|
||||
|
||||
# seed database
|
||||
$ docker-compose exec backend yarn run db:seed
|
||||
```
|
||||
|
||||
**Wipe out Neo4j database in Docker**
|
||||
|
||||
To wipe out your neo4j database and delete the volumes send command:
|
||||
|
||||
```bash
|
||||
# open another terminal and run
|
||||
$ docker-compose down -v
|
||||
```
|
||||
|
||||
**Video Tutorial**
|
||||
|
||||
{% hint style="info" %}
|
||||
TODO: Link to video
|
||||
{% endhint %}
|
||||
|
||||
#### Development with Kubernetes
|
||||
|
||||
For further informations see also our [Kubernetes documentation](https://github.com/Human-Connection/Human-Connection/tree/9bede1913b829a5c2916fc206c1fe4c83c49a4bc/kubernetes.md).
|
||||
|
||||
## Local Installation
|
||||
|
||||
#### Install the dependencies
|
||||
|
||||
```bash
|
||||
$ yarn install
|
||||
$ cd backend && yarn install
|
||||
$ cd ../webapp && yarn install
|
||||
$ cd ..
|
||||
```
|
||||
|
||||
#### Copy Environment Variables
|
||||
|
||||
```bash
|
||||
$ cp cypress.env.template.json cypress.env.json
|
||||
$ cp backend/.env.template backend/.env
|
||||
$ cp webapp/.env.template webapp/.env
|
||||
```
|
||||
|
||||
Configure the new files according to your needs and your local setup.
|
||||
|
||||
@ -25,4 +25,4 @@
|
||||
"neo4j-driver": "^1.7.3",
|
||||
"npm-run-all": "^4.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
20
testing.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Testing Guide
|
||||
|
||||
## End-to-End Testing
|
||||
|
||||
To test all the pieces together, from the user perspective, we use integration tests. They also show if the the backend and the frontend are working as expected in conjunction and also if the browser likes our app.
|
||||
|
||||
[more...](cypress/README.md)
|
||||
|
||||
## Component Testing
|
||||
|
||||
Individual Vue Components should also be documented and tested properly. This guarantees that they are reusable and the api gets more solid in the process.
|
||||
|
||||
[more...](webapp/testing.md)
|
||||
|
||||
## Unit Testing
|
||||
|
||||
Expecially the Backend relies on Unit Tests, as there are no Vue Components.
|
||||
|
||||
[more...](backend/testing.md)
|
||||
|
||||
@ -1,40 +1,33 @@
|
||||
<p align="center">
|
||||
<a href="https://human-connection.org"><img align="center" src="static/img/sign-up/humanconnection.png" height="200" alt="Human Connection" /></a>
|
||||
</p>
|
||||
# Webapp
|
||||
|
||||
# NITRO Web
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Web)
|
||||
[](https://github.com/Human-Connection/Nitro-Web/blob/master/LICENSE.md)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Web?ref=badge_shield)
|
||||
[](https://discord.gg/6ub73U3)
|
||||

|
||||
|
||||

|
||||
## Installation
|
||||
|
||||
## Build Setup
|
||||
|
||||
|
||||
|
||||
### Install
|
||||
``` bash
|
||||
```bash
|
||||
# install all dependencies
|
||||
$ yarn install
|
||||
```
|
||||
|
||||
Copy:
|
||||
```
|
||||
|
||||
```text
|
||||
cp .env.template .env
|
||||
cp cypress.env.template.json cypress.env.json
|
||||
```
|
||||
|
||||
Configure the files according to your needs and your local setup.
|
||||
|
||||
### Development
|
||||
``` bash
|
||||
### Build for Development
|
||||
|
||||
```bash
|
||||
# serve with hot reload at localhost:3000
|
||||
$ yarn dev
|
||||
```
|
||||
|
||||
### Build for production
|
||||
``` bash
|
||||
### Build for Production
|
||||
|
||||
```bash
|
||||
# build for production and launch server
|
||||
$ yarn build
|
||||
$ yarn start
|
||||
@ -42,27 +35,10 @@ $ yarn start
|
||||
|
||||
## Styleguide
|
||||
|
||||
All reusable Components (for example avatar) should be done inside the [Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) repository.
|
||||
All reusable Components \(for example avatar\) should be done inside the [Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide) repository.
|
||||
|
||||

|
||||

|
||||
|
||||
More information can be found here: https://github.com/Human-Connection/Nitro-Styleguide
|
||||
More information can be found here: [https://github.com/Human-Connection/Nitro-Styleguide](https://github.com/Human-Connection/Nitro-Styleguide)
|
||||
|
||||
|
||||
If you need to change something in the styleguide and want to see the effects on the frontend immediately, then we have you covered.
|
||||
You need to clone the styleguide to the parent directory `../Nitro-Styleguide` and run `yarn && yarn run dev`. After that you run `yarn run dev:styleguide` instead of `yarn run dev` and you will see your changes reflected inside the fronten!
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
You can help translating the interface by joining us on [lokalise.co](https://lokalise.co/public/556252725c18dd752dd546.13222042/).
|
||||
|
||||
Thanks lokalise.co that we can use your premium account!
|
||||
|
||||
<a href="(https://lokalise.co/public/556252725c18dd752dd546.13222042/)."><img src="lokalise.png" alt="localise.co" height="32px" /></a>
|
||||
|
||||
## Attributions
|
||||
|
||||
<div>Locale Icons made by <a href="http://www.freepik.com/" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Web?ref=badge_large)
|
||||
If you need to change something in the styleguide and want to see the effects on the frontend immediately, then we have you covered. You need to clone the styleguide to the parent directory `../Nitro-Styleguide` and run `yarn && yarn run dev`. After that you run `yarn run dev:styleguide` instead of `yarn run dev` and you will see your changes reflected inside the fronten!
|
||||
|
||||
@ -5,3 +5,4 @@
|
||||
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).
|
||||
|
||||
@ -5,3 +5,4 @@
|
||||
The components directory contains your Vue.js Components.
|
||||
|
||||
_Nuxt.js doesn't supercharge these components._
|
||||
|
||||
7
webapp/components/Category/Readme.md
Normal file
@ -0,0 +1,7 @@
|
||||
### Example
|
||||
|
||||
Category "IT, Internet & Data Privacy" with icon "mouse-cursor"
|
||||
|
||||
```
|
||||
<hc-category icon="mouse-pointer" name="IT, Internet & Data Privacy" />
|
||||
```
|
||||
35
webapp/components/Category/index.spec.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Category from './index'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Category', () => {
|
||||
let icon
|
||||
let name
|
||||
|
||||
let Wrapper = () => {
|
||||
return shallowMount(Category, {
|
||||
localVue,
|
||||
propsData: {
|
||||
icon,
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('given Strings for Icon and Name', () => {
|
||||
beforeEach(() => {
|
||||
icon = 'mouse-cursor'
|
||||
name = 'Peter'
|
||||
})
|
||||
|
||||
it('shows Name', () => {
|
||||
expect(Wrapper().text()).toContain('Peter')
|
||||
})
|
||||
it('shows Icon Svg', () => {
|
||||
expect(Wrapper().contains('svg'))
|
||||
})
|
||||
})
|
||||
})
|
||||
19
webapp/components/Category/index.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<ds-tag>
|
||||
<ds-icon
|
||||
size="large"
|
||||
:name="icon"
|
||||
/>
|
||||
{{ name }}
|
||||
</ds-tag>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HcCategory',
|
||||
props: {
|
||||
icon: { type: String, required: true },
|
||||
name: { type: String, required: true }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -4,16 +4,15 @@
|
||||
style="padding-left: 40px; font-weight: bold;"
|
||||
color="soft"
|
||||
>
|
||||
<ds-icon name="ban" /> {{ this.$t('comment.content.unavailable-placeholder') }}
|
||||
<ds-icon name="ban" />
|
||||
{{ this.$t('comment.content.unavailable-placeholder') }}
|
||||
</ds-text>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="{'comment': true, 'disabled-content': (comment.deleted || comment.disabled)}"
|
||||
>
|
||||
<ds-space
|
||||
margin-bottom="x-small"
|
||||
>
|
||||
<ds-space margin-bottom="x-small">
|
||||
<hc-user :user="author" />
|
||||
</ds-space>
|
||||
<no-ssr>
|
||||
@ -32,13 +31,13 @@
|
||||
style="padding-left: 40px;"
|
||||
v-html="comment.contentExcerpt"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import HcUser from '~/components/User.vue'
|
||||
import HcUser from '~/components/User'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
|
||||
export default {
|
||||
|
||||
@ -111,9 +111,8 @@ export default {
|
||||
|
||||
if (this.isOwner && this.resourceType === 'user') {
|
||||
routes.push({
|
||||
name: this.$t(`settings.data.name`),
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
callback: () => this.$router.push('/settings'),
|
||||
name: this.$t(`settings.name`),
|
||||
path: '/settings',
|
||||
icon: 'edit'
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,9 +8,7 @@
|
||||
v-router-link
|
||||
class="post-link"
|
||||
:href="href(post)"
|
||||
>
|
||||
{{ post.title }}
|
||||
</a>
|
||||
>{{ post.title }}</a>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- TODO: replace editor content with tiptap render view -->
|
||||
<ds-space margin-bottom="large">
|
||||
@ -32,7 +30,7 @@
|
||||
</ds-space>
|
||||
<ds-space
|
||||
margin="small"
|
||||
style="position: absolute; bottom: 44px; z-index: 1;"
|
||||
style="position: absolute; bottom: 44px;"
|
||||
>
|
||||
<!-- TODO: find better solution for rendering errors -->
|
||||
<no-ssr>
|
||||
@ -53,11 +51,13 @@
|
||||
</div>
|
||||
<div style="display: inline-block; float: right">
|
||||
<span :style="{ opacity: post.shoutedCount ? 1 : .5 }">
|
||||
<ds-icon name="bullhorn" /> <small>{{ post.shoutedCount }}</small>
|
||||
<ds-icon name="bullhorn" />
|
||||
<small>{{ post.shoutedCount }}</small>
|
||||
</span>
|
||||
|
||||
<span :style="{ opacity: post.commentsCount ? 1 : .5 }">
|
||||
<ds-icon name="comments" /> <small>{{ post.commentsCount }}</small>
|
||||
<ds-icon name="comments" />
|
||||
<small>{{ post.commentsCount }}</small>
|
||||
</span>
|
||||
<no-ssr>
|
||||
<content-menu
|
||||
@ -72,7 +72,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcUser from '~/components/User.vue'
|
||||
import HcUser from '~/components/User'
|
||||
import ContentMenu from '~/components/ContentMenu'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
@ -118,27 +118,25 @@ export default {
|
||||
.post-card {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.ds-card-footer {
|
||||
z-index: 1;
|
||||
}
|
||||
/*.ds-card-footer {
|
||||
}*/
|
||||
|
||||
.content-menu {
|
||||
display: inline-block;
|
||||
margin-left: $space-xx-small;
|
||||
margin-right: -$space-x-small;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.post-link {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-indent: -999999px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-link {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-indent: -999999px;
|
||||
}
|
||||
</style>
|
||||
|
||||
7
webapp/components/RelativeDateTime/Readme.md
Normal file
@ -0,0 +1,7 @@
|
||||
### Example
|
||||
|
||||
Relative time from 08.03.2017
|
||||
|
||||
```
|
||||
<hc-relative-date-time dateTime="03.08.2017" />
|
||||
```
|
||||
79
webapp/components/RelativeDateTime/index.spec.js
Normal file
@ -0,0 +1,79 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import RelativeDateTime from './index'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
describe('RelativeDateTime', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let locale
|
||||
let dateTime
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$i18n: {
|
||||
locale: () => locale
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let Wrapper = () => {
|
||||
return shallowMount(RelativeDateTime, {
|
||||
mocks,
|
||||
localVue,
|
||||
propsData: {
|
||||
dateTime
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('given a String as dateTime', () => {
|
||||
beforeEach(() => {
|
||||
dateTime = '08.03.2017'
|
||||
})
|
||||
|
||||
it('translates', () => {
|
||||
expect(Wrapper().text()).toContain('08/03/2017')
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a Date object as dateTime', () => {
|
||||
beforeEach(() => {
|
||||
dateTime = new Date()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(Wrapper().is('span')).toBe(true)
|
||||
})
|
||||
|
||||
describe("locale == 'en'", () => {
|
||||
beforeEach(() => {
|
||||
locale = 'en'
|
||||
})
|
||||
|
||||
it('translates', () => {
|
||||
expect(Wrapper().text()).toContain('today at')
|
||||
})
|
||||
})
|
||||
|
||||
describe("locale == 'gibberish'", () => {
|
||||
beforeEach(() => {
|
||||
locale = 'gibberish'
|
||||
})
|
||||
|
||||
it('translates', () => {
|
||||
expect(Wrapper().text()).toContain('today at')
|
||||
})
|
||||
})
|
||||
|
||||
describe("locale == 'de'", () => {
|
||||
beforeEach(() => {
|
||||
locale = 'de'
|
||||
})
|
||||
|
||||
it('translates', () => {
|
||||
expect(Wrapper().text()).toContain('heute um')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
33
webapp/components/RelativeDateTime/index.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<span>{{ relativeDateTime }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import formatRelative from 'date-fns/formatRelative'
|
||||
import { enUS, de, nl, fr, pt, es /*, pl*/ } from 'date-fns/locale'
|
||||
const locales = {
|
||||
en: enUS,
|
||||
de,
|
||||
nl,
|
||||
fr,
|
||||
es,
|
||||
pt
|
||||
// pl
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'HcRelativeDateTime',
|
||||
props: {
|
||||
dateTime: {
|
||||
type: [Date, String],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
relativeDateTime() {
|
||||
let locale = locales[this.$i18n.locale() || 'en']
|
||||
return formatRelative(new Date(this.dateTime), new Date(), { locale })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
7
webapp/components/Tag/Readme.md
Normal file
@ -0,0 +1,7 @@
|
||||
### Example
|
||||
|
||||
Tag "Liebe"
|
||||
|
||||
```
|
||||
<hc-tag name="Liebe" />
|
||||
```
|
||||
29
webapp/components/Tag/index.spec.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Tag from './index'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Tag', () => {
|
||||
let name
|
||||
|
||||
let Wrapper = () => {
|
||||
return shallowMount(Tag, {
|
||||
localVue,
|
||||
propsData: {
|
||||
name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('given a String for Name', () => {
|
||||
beforeEach(() => {
|
||||
name = 'Liebe'
|
||||
})
|
||||
|
||||
it('shows Name', () => {
|
||||
expect(Wrapper().text()).toContain('Liebe')
|
||||
})
|
||||
})
|
||||
})
|
||||
15
webapp/components/Tag/index.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<ds-tag>
|
||||
<ds-icon name="tag" />
|
||||
{{ name }}
|
||||
</ds-tag>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HcTag',
|
||||
props: {
|
||||
name: { type: String, required: true }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,5 +1,5 @@
|
||||
import { config, mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'
|
||||
import User from './User.vue'
|
||||
import User from './index'
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
@ -15,7 +15,7 @@ localVue.use(Styleguide)
|
||||
|
||||
localVue.filter('truncate', filter)
|
||||
|
||||
describe('User.vue', () => {
|
||||
describe('User', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let propsData
|
||||
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div v-if="!user || ((user.disabled || user.deleted) && !isModerator)">
|
||||
<div style="display: inline-block; float: left; margin-right: 4px; height: 100%; vertical-align: middle;">
|
||||
<div
|
||||
style="display: inline-block; float: left; margin-right: 4px; height: 100%; vertical-align: middle;"
|
||||
>
|
||||
<ds-avatar
|
||||
style="display: inline-block; vertical-align: middle;"
|
||||
size="32px"
|
||||
@ -10,9 +12,7 @@
|
||||
<b
|
||||
class="username"
|
||||
style="vertical-align: middle;"
|
||||
>
|
||||
Anonymus
|
||||
</b>
|
||||
>Anonymus</b>
|
||||
</div>
|
||||
</div>
|
||||
<dropdown
|
||||
@ -33,7 +33,9 @@
|
||||
@mouseover="openMenu(true)"
|
||||
@mouseleave="closeMenu(true)"
|
||||
>
|
||||
<div style="display: inline-block; float: left; margin-right: 4px; height: 100%; vertical-align: middle;">
|
||||
<div
|
||||
style="display: inline-block; float: left; margin-right: 4px; height: 100%; vertical-align: middle;"
|
||||
>
|
||||
<ds-avatar
|
||||
:image="user.avatar"
|
||||
:name="user.name"
|
||||
@ -45,16 +47,28 @@
|
||||
<b
|
||||
class="username"
|
||||
style="vertical-align: middle;"
|
||||
>{{ user.name | truncate(trunc, 18) }}</b>
|
||||
</div>
|
||||
<!-- Time -->
|
||||
<div
|
||||
v-if="dateTime"
|
||||
style="display: inline;"
|
||||
>
|
||||
<ds-text
|
||||
align="right"
|
||||
size="small"
|
||||
color="soft"
|
||||
>
|
||||
{{ user.name | truncate(trunc, 18) }}
|
||||
</b>
|
||||
<ds-icon name="clock" />
|
||||
<no-ssr>
|
||||
<hc-relative-date-time :date-time="dateTime" />
|
||||
</no-ssr>
|
||||
</ds-text>
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template
|
||||
slot="popover"
|
||||
>
|
||||
<template slot="popover">
|
||||
<div style="min-width: 250px">
|
||||
<hc-badges
|
||||
v-if="user.badges && user.badges.length"
|
||||
@ -68,11 +82,10 @@
|
||||
style="margin-top: 5px"
|
||||
bold
|
||||
>
|
||||
<ds-icon name="map-marker" /> {{ user.location.name }}
|
||||
<ds-icon name="map-marker" />
|
||||
{{ user.location.name }}
|
||||
</ds-text>
|
||||
<ds-flex
|
||||
style="margin-top: -10px"
|
||||
>
|
||||
<ds-flex style="margin-top: -10px">
|
||||
<ds-flex-item class="ds-tab-nav-item">
|
||||
<ds-space margin="small">
|
||||
<ds-number
|
||||
@ -125,21 +138,25 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
||||
import HcFollowButton from '~/components/FollowButton.vue'
|
||||
import HcBadges from '~/components/Badges.vue'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'HcUser',
|
||||
components: {
|
||||
HcRelativeDateTime,
|
||||
HcFollowButton,
|
||||
HcBadges,
|
||||
Dropdown
|
||||
},
|
||||
props: {
|
||||
user: { type: Object, default: null },
|
||||
trunc: { type: Number, default: null }
|
||||
trunc: { type: Number, default: null },
|
||||
dateTime: { type: [Date, String], default: null }
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@ -98,6 +98,10 @@ export default app => {
|
||||
}
|
||||
}
|
||||
}
|
||||
socialMedia {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@ -5,3 +5,4 @@
|
||||
This directory contains your Application Layouts.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
"following": "Folgt",
|
||||
"shouted": "Empfohlen",
|
||||
"commented": "Kommentiert",
|
||||
"userAnonym": "Anonymus"
|
||||
"userAnonym": "Anonymus",
|
||||
"socialMedia": "Wo sonst finde ich"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Suchen",
|
||||
@ -53,6 +54,11 @@
|
||||
},
|
||||
"languages": {
|
||||
"name": "Sprachen"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Soziale Medien",
|
||||
"submit": "Link hinzufügen",
|
||||
"success": "Profil aktualisiert"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -16,7 +16,8 @@
|
||||
"following": "Following",
|
||||
"shouted": "Shouted",
|
||||
"commented": "Commented",
|
||||
"userAnonym": "Anonymous"
|
||||
"userAnonym": "Anonymous",
|
||||
"socialMedia": "Where else can I find"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search",
|
||||
@ -53,6 +54,11 @@
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Social media",
|
||||
"submit": "Add link",
|
||||
"success": "Updated user profile"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
**This directory is not required, you can delete it if you don't want to use it.**
|
||||
|
||||
This directory contains your application middleware.
|
||||
The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts).
|
||||
This directory contains your application middleware. The middleware lets you define custom function to be ran before rendering a page or a group of pages \(layouts\).
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
|
||||
|
||||
@ -47,12 +47,13 @@
|
||||
"graphql": "~14.2.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkify-it": "~2.1.0",
|
||||
"nuxt": "~2.4.5",
|
||||
"nuxt": "~2.6.2",
|
||||
"nuxt-env": "~0.1.0",
|
||||
"stack-utils": "^1.0.2",
|
||||
"string-hash": "^1.1.3",
|
||||
"tiptap": "^1.14.0",
|
||||
"tiptap-extensions": "^1.14.0",
|
||||
"v-tooltip": "~2.0.0-rc.33",
|
||||
"tiptap-extensions": "^1.15.0",
|
||||
"v-tooltip": "~2.0.1",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vue-sweetalert-icons": "~3.2.0",
|
||||
@ -61,7 +62,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.4.3",
|
||||
"@babel/preset-env": "~7.4.3",
|
||||
"@vue/cli-shared-utils": "~3.5.1",
|
||||
"@vue/cli-shared-utils": "~3.6.0",
|
||||
"@vue/eslint-config-prettier": "~4.0.1",
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
@ -75,7 +76,7 @@
|
||||
"eslint-plugin-vue": "~5.2.2",
|
||||
"jest": "~24.7.1",
|
||||
"node-sass": "~4.11.0",
|
||||
"nodemon": "~1.18.10",
|
||||
"nodemon": "~1.18.11",
|
||||
"prettier": "~1.14.3",
|
||||
"sass-loader": "~7.1.0",
|
||||
"vue-jest": "~3.0.4",
|
||||
|
||||
6
webapp/pages.md
Normal file
@ -0,0 +1,6 @@
|
||||
# PAGES
|
||||
|
||||
This directory contains your Application Views and Routes. The framework reads all the `*.vue` files inside this directory and create the router of your application.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
# PAGES
|
||||
|
||||
This directory contains your Application Views and Routes.
|
||||
The framework reads all the `*.vue` files inside this directory and create the router of your application.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
|
||||
@ -32,22 +32,26 @@ export default {
|
||||
name: this.$t('admin.dashboard.name'),
|
||||
path: `/admin`
|
||||
},
|
||||
{
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('admin.users.name'),
|
||||
path: `/admin/users`
|
||||
},
|
||||
{
|
||||
}, */
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('admin.organizations.name'),
|
||||
path: `/admin/organizations`
|
||||
},
|
||||
{
|
||||
}, */
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('admin.pages.name'),
|
||||
path: `/admin/pages`
|
||||
},
|
||||
{
|
||||
}, */
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('admin.notifications.name'),
|
||||
path: `/admin/notifications`
|
||||
},
|
||||
}, */
|
||||
{
|
||||
name: this.$t('admin.categories.name'),
|
||||
path: `/admin/categories`
|
||||
@ -55,11 +59,12 @@ export default {
|
||||
{
|
||||
name: this.$t('admin.tags.name'),
|
||||
path: `/admin/tags`
|
||||
},
|
||||
{
|
||||
}
|
||||
// TODO implement
|
||||
/* {
|
||||
name: this.$t('admin.settings.name'),
|
||||
path: `/admin/settings`
|
||||
}
|
||||
} */
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +149,8 @@ export default {
|
||||
.post-add-button {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
top: 100vh;
|
||||
left: 100vw;
|
||||
top: 98vh;
|
||||
left: 98vw;
|
||||
transform: translate(-120%, -120%);
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
|
||||